Jeff Quast

Python Developer

Joyent Provisioning from Custom Image

As mentioned in another blog post about abusing the Hover API, I am using joyent.com to provision a Solaris build server agent. I prepared an image with the service teamcity-agent, which, when enabled, will be ready to build any project build configurations which require SunOs. As it is quite expensive to run a Solaris host on Joyent all year-long (several hundreds of dollars), I thought it worthwhile to spend at least a few hours to provision a compute instance for testing pexpect builds on-demand through TeamCity.

This requires

Create a custom image from Compute

With a Compute Instance tailored to support python builds and contain the TeamCity agent, I was able to create a TeamCity job to be executed manually to store a Custom Image for later provisioning, requiring:

  • %machine-uuid% - The UUID of the Computer instance
  • %name% - The friendly-name from the Custom Image to be created
  • %description% - The friendly description of the Image.
  • %build.counter% - The image is versioned using the TeamCity build counter.
#!/bin/bash
# https://apidocs.joyent.com/cloudapi/#CreateImageFromMachine
set -e
set -o pipefail

[ -z $TEMP ] && TEMP=/tmp

output=$(mktemp $TEMP/createimagefrommachine.XXXX)
sdc-createimagefrommachine \
    --machine %machine-uuid% \
    --name %name% \
    --description "%description%" \
    --imageVersion="1.%build.counter%" \
> "${output}"

image_id=$(json -a id < "${output}")
state=$(json -a state < "${output}")
if [ -z $image_id ]; then
   echo "Unable to determine image uuid."
   echo "Presumably, an error occurred. Please see program output:"
   cat "${output}"
fi
rm -f "${output}"

while [ X"$state" != X"active" ]; do
    echo "Image state is $state"
    if [ X"$state" == X"failed" ]; then
        sdc-getimage $image_id
        exit 1
    fi
    sleep 10
    state=$(sdc-getimage $image_id | json -a state)
done

echo "Image state is ${state}"
echo "Image UUID is ${image_uuid}

Create a Machine instance from Custom Image

With the custom image requirement satisfied, you need only provide

  • %image-name% - the friendly-name used for the base image to provision.
  • %name% - the friendly-name used to provision the instance (sunos.pexpect.org)
  • %package% - the compute package name, indicating vCpu, memory, and disk resources.
#!/bin/bash
# https://apidocs.joyent.com/cloudapi/#CreateMachine
set -e
set -o pipefail

[ -z $TEMP ] && TEMP=/tmp
output="$(mktemp $TEMP/sdc-createmachine.XXXXXX)"

# First, discover if a machine by this name
# is already been provisioned!
sdc-listmachines | json -c 'this.name=="'%name%'"' > "${output}"
machine_id=$(json -a id < "${output}")

if [ ! -z "${machine_id}" ]; then
     echo "A machine by this name has already been provisioned."
else
    # Given the package name (eg. g3-standard-0.25-smartos),
    # discover the UUID.
    package_uuid=$(sdc-listpackages \
                   | json -a id \
                          -c 'this.name=="%package%"')
    if [ -z $package_uuid ]; then
        echo "No package found by name %package%"
        echo "Available packages:"
        sdc-listpackages | json -a name
        exit 1
    fi

    # Given the image name (eg. teamcity-agentbase),
    # discover the UUID.
    image_uuid=$(sdc-listimages --public=false \
                 | json -a id \
                        -c 'this.name=="%image-name%"')
    if [ -z $image_uuid ]; then
        echo "No image found by name %image-name%"
        echo "Available image names:"
        sdc-listimages --public=false | json -a name
        exit 1
    fi
    echo "image-id is ${image_uuid}"

    # Next, verify the image uuid is active
    image_status=$(sdc-getimage ${image_uuid} | json -a state)
    if [ X"$image_status" != X"active" ]; then
        echo "Expected image status should be 'active' but state=${image_status}" 2>&1
        echo "Available images:"
        sdc-listimages --public=false
        exit 1
    fi

    # finally, create the machine with the given
    # image uuid and package uuid and optional name.
    sdc-createmachine \
        --image=${image_uuid} \
        --enable-firewall=true \
        --package=${package_uuid} \
        --name="%name%" \
    > "${output}"

    machine_id=$(json < "${output}" -a id)
    if [ -z ${machine_id} ]; then
        echo "Failed to get machine uuid from sdc-createmachine."
        echo "Presumably, an error occurred. See program output:"
        cat "${output}"
        rm -f "${output}"
        exit 1
    fi
fi

echo "machine-id: ${machine_id}"
state=$(json -a state < "${output}")

while [ X"$state" != X"running" ]; do
    echo "Machine state is $state"
    sleep 10
    sdc-getmachine $machine_id > "${output}"
    state=$(json -a state < "${output}")
done
echo "Machine state is ${state}"

primaryIp=$(json -a primaryIp < "${output}")
if [ -z ${primaryIp} ]; then
    echo "Failed to get primaryIp from sdc-listmachines."
    echo "Presumably, an error occurred. Please see program output:"
    cat "{$output}"
    rm -f "${output}"
    exit 1
fi
rm -f "${output}"

echo "primaryIp is ${primaryIp}"
echo "##teamcity[setParameter name='primaryIp' value='"${primaryIp}"']"

Using TeamCity

JetBrains was kind enough to finally respond to my request for an Open Source Software license, which I certainly plan to take advantage of for all of my projects over the coming year. As pexpect is used as part of the IPython project, which JetBrains uses in their Commercially Licensed product PyCharm it is good to see us mutually benefit by using their TeamCity Continuous Integration service.

I must re-apply each year, so I'm still concerned that I might be locked out of my own builds if they fail to renew my license, but it more than satisfies my needs of Parallel Builds, ease of Administration, E-mail notifications, GitHub push API support, and GitHub commit status notifications.

I will miss using Travis-CI but their builds have intermittent errors, do not support Solaris, OSX, FreeBSD, or Windows environments, and there have been many tests which fail on Travis-CI that cannot be reproduced. However, it is still free, and can be set up in less than an hours' effort.