Jenkins Integration

Changes integrates extremely with Jenkins as a build manager, however it will require you to have a very specialized job for running a build.

Creating the Job

This changes rapidly and documentation is not maintained for the internals of the generic job.

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description></description>
  <logRotator class="hudson.tasks.LogRotator">
    <daysToKeep>7</daysToKeep>
    <numToKeep>1000</numToKeep>
    <artifactDaysToKeep>-1</artifactDaysToKeep>
    <artifactNumToKeep>100</artifactNumToKeep>
  </logRotator>
  <keepDependencies>false</keepDependencies>
  <properties>
    <hudson.model.ParametersDefinitionProperty>
      <parameterDefinitions>
        <hudson.model.StringParameterDefinition>
          <name>REVISION</name>
          <description>A commit, branch name, or something else recognizable as a tree.
</description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>PATCH_URL</name>
          <description></description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>REPO_URL</name>
          <description></description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.ChoiceParameterDefinition>
          <name>REPO_VCS</name>
          <description></description>
          <choices class="java.util.Arrays$ArrayList">
            <a class="string-array">
              <string>git</string>
              <string>hg</string>
            </a>
          </choices>
        </hudson.model.ChoiceParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>SCRIPT</name>
          <description></description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>RESET_SCRIPT</name>
          <description>Cleanup tasks to run after the generic job completes. Intended for use by reset-generic jobs.
          </description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>CHANGES_PID</name>
          <description>Changes Project Slug</description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>CHANGES_BID</name>
          <description>Changes Job ID (DO NOT ENTER MANUALLY)</description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <org.jvnet.jenkins.plugins.nodelabelparameter.LabelParameterDefinition plugin="nodelabelparameter@1.5.1">
          <name>CLUSTER</name>
          <description></description>
          <defaultValue></defaultValue>
          <allNodesMatchingLabel>false</allNodesMatchingLabel>
          <triggerIfResult>allCases</triggerIfResult>
          <nodeEligibility class="org.jvnet.jenkins.plugins.nodelabelparameter.node.AllNodeEligibility"/>
        </org.jvnet.jenkins.plugins.nodelabelparameter.LabelParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>WORK_PATH</name>
          <description>Working directory to run SCRIPT from. Relative to REPO_ROOT.</description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
        <hudson.model.StringParameterDefinition>
          <name>C_WORKSPACE</name>
          <description>Custom workspace directory.</description>
          <defaultValue></defaultValue>
        </hudson.model.StringParameterDefinition>
      </parameterDefinitions>
    </hudson.model.ParametersDefinitionProperty>
    <com.sonyericsson.rebuild.RebuildSettings plugin="rebuild@1.20">
      <autoRebuild>true</autoRebuild>
    </com.sonyericsson.rebuild.RebuildSettings>
  </properties>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <authToken/>
  <triggers/>
  <concurrentBuild>true</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>#!/bin/bash -ex

/var/lib/jenkins/build-steps/generic-build
</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers>
    <hudson.tasks.ArtifactArchiver>
      <artifacts>**/junit.xml,**/coverage.xml,**/tests.json,**/jobs.json</artifacts>
      <latestOnly>false</latestOnly>
      <allowEmptyArchive>true</allowEmptyArchive>
    </hudson.tasks.ArtifactArchiver>
    <hudson.tasks.BuildTrigger>
      <childProjects>reset-generic</childProjects>
      <threshold>
        <name>FAILURE</name>
        <ordinal>2</ordinal>
        <color>RED</color>
        <completeBuild>true</completeBuild>
      </threshold>
    </hudson.tasks.BuildTrigger>
  </publishers>
</project>

Example scripts based on git are included for reference. Note that REPO_PATH is a global variable that is assumed to exist. The reset-generic job here is an optional, sample downstream job that can be run to execute cleanup tasks passed through the RESET_SCRIPT parameter. Running cleanup tasks outside the generic job has the advantage of not delaying build results from the generic job being posted back to Changes.

Master Build Step

#!/bin/bash -ex

echo `whoami`@$HOSTNAME
uname -a

# nothing works without ssh keys, so let's straight up error
# out of theres no keys/agent present
ssh-agent -s
ssh-add -l

REPO_PATH=$WORKSPACE/$CHANGES_PID

if [ -z $REVISION ]; then
  if [ "$REPO_VCS" = "hg" ]; then
    REVISION=default
  else
    REVISION=master
  fi
fi

if [ "$REPO_VCS" = "git" ]; then
  git-clone $REPO_PATH $REPO_URL $REVISION
  git-patch $REPO_PATH $PATCH_URL
else
  hg-clone $REPO_PATH $REPO_URL $REVISION
  hg-patch $REPO_PATH $PATCH_URL
fi

# clean up any artifacts which might be present
for artifact_name in "junit.xml coverage.xml jobs.json tests.json"; do
	find . -name $artifact_name -delete
done

SCRIPT_PATH=/tmp/$(mktemp build-step.XXXXXXXXXX)
echo "$SCRIPT" | tee $SCRIPT_PATH
chmod +x $SCRIPT_PATH

pushd $REPO_PATH

if [ ! -z $WORK_PATH ]; then
    pushd $WORK_PATH
fi

$SCRIPT_PATH

Fetching the Revision

#!/bin/bash -eux

if [ ! -d $REPO_PATH/.git ]; then
        git clone $REPO_URL $REPO_PATH
        pushd $REPO_PATH
else
        pushd $REPO_PATH && git fetch --all
        git remote prune origin
fi

git clean -fdx

if ! git reset --hard $REVISION ; then
        git reset --hard origin/master
        echo "Failed to update to $REVISION, falling back to master"
fi

Applying the Patch

#!/bin/bash -eux

WORKSPACE_DIR=$(pwd)

pushd $REPO_PATH
if [ ! -z "${PATCH_URL:-}" ]; then
        curl -o ${WORKSPACE_DIR}/PATCH $PATCH_URL
        git apply ${WORKSPACE_DIR}/PATCH
fi

Running Tests

This step is arbitrary based on your platform. In Python this might be something like:

py.test --junit=junit.xml