Table of contents
Introduction
Apache Yetus is a toolbox for building and releasing software. One of the tools is a generalized framework for performing patch and full build testing for continuous integration systems. It supports a wide variety of build tools and features. As a result, it can be overwhelming to get started using it. In this blog post, I’ll run through a quick example of configuring Jenkins Multibranch Pipelines using the Github Branch Source Plugin as a starting point for your own usage. In a follow-up post, I’ll cover a more advanced example, custom Docker images, and a few different use cases.
Jenkinsfile
A file is expected to be committed to the source tree to use Multibranch Pipelines. That file contains the pipeline definition. By default, this file is expected to be named Jenkinsfile
, but it may be called anything you want as long as Jenkins is told the name at job definition time. In this example, we will also assume it is called Jenkinsfile
.
Credential Pre-work
In order for Jenkins and Apache Yetus to be able to communicate with Github, at least one username and OAuth token combination needs to be generated, as described in the Github documentation. Due to the suboptimal Github authorization system, repo:status and admin:repo_hook are the required scopes.
This username/token combination should be stored as described by the Jenkins documentation. The credential should be configured such that it is a username and password combination, where the password is the OAuth token for that account. In this example, the id of the credential is assumed to be ‘yetus-github-account’ but may be anything you want.
Using the Apache Yetus Convenience Docker Image
Pipelines on Jenkins can be configured similarly to other CI systems where a Docker image may be used as the environment. Apache Yetus publishes convenience images on Docker Hub that may also be used in this way. With it, you get a reasonably functional image with quite a few development tools and all of the Apache Yetus bits built into it.
A Jenkinsfile
that uses the Apache Yetus image should look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
pipeline { agent { docker { image "apache/yetus:0.9.0" //label 'foo||bar' } } options { buildDiscarder(logRotator(numToKeepStr: '5')) timeout (time: 1, unit: 'HOURS') timestamps() checkoutToSubdirectory('src') } environment { // must be relative to the workspace and match the above checkout SOURCEDIR='src' YETUS_PATCHDIR = 'out' } stages { stage ('apache-yetus') { steps { withCredentials([usernamePassword(credentialsId: 'yetus-github-account', passwordVariable: 'GITHUB_PASSWORD', usernameVariable: 'GITHUB_USER')]) { sh '''#!/usr/bin/env bash cd "${WORKSPACE}" # This works around a bug in Jenkins where docker agents # may not have the JENKINS_URL set (See YETUS-786/JENKINS-55752) export JENKINS_URL=${JENKINS_URL:-${HUDSON_URL}} # clean and make a new directory for our output artifacts, temporary # storage, etc just in case the workspace directory # delete in post is removed if [[ -d "${WORKSPACE}/${YETUS_PATCHDIR}" ]]; then rm -rf "${WORKSPACE}/${YETUS_PATCHDIR}" fi mkdir -p "${WORKSPACE}/${YETUS_PATCHDIR}" YETUS_ARGS+=("--patch-dir=${WORKSPACE}/${YETUS_PATCHDIR}") # where the source is located YETUS_ARGS+=("--basedir=${WORKSPACE}/${SOURCEDIR}") # lots of different output formats YETUS_ARGS+=("--brief-report-file=${WORKSPACE}/${YETUS_PATCHDIR}/brief.txt") YETUS_ARGS+=("--console-report-file=${WORKSPACE}/${YETUS_PATCHDIR}/console.txt") YETUS_ARGS+=("--html-report-file=${WORKSPACE}/${YETUS_PATCHDIR}/report.html") # enable writing back to Github YETUS_ARGS+=(--github-password="${GITHUB_PASSWORD}") YETUS_ARGS+=(--github-user="${GITHUB_USER}") # turn on all the plug-ins YETUS_ARGS+=("--plugins=all") # URL for user-side presentation in reports and such to our artifacts # (needs to match the archive bits below) YETUS_ARGS+=("--build-url-artifacts=artifact/${YETUS_PATCHDIR}") # if this isn't a PR, run a full build against the source tree YETUS_ARGS+=("--empty-patch") # execute! test-patch "${YETUS_ARGS[@]}" ''' } } } } post { always { script { // Publish the HTML report so that it can be looked at // Has to be relative to WORKSPACE. archiveArtifacts "${env.YETUS_PATCHDIR}/**" publishHTML (target: [ allowMissing: true, keepAll: true, alwaysLinkToLastBuild: true, // Has to be relative to WORKSPACE reportDir: "${env.YETUS_PATCHDIR}", reportFiles: 'report.html', reportName: 'Yetus Report' ]) } } // Github Branch Source plug-in will quickly fill Jenkins // build hosts with PR directories. This cleanup stanza kills // any leftover processes/containers and frees disk space // on exit cleanup() { deleteDir() } } } |
The ‘agent’ section defines what Docker image to use. In this example, I am using Apache Yetus version 0.9.0. In many setups, it may be desirable to configure the container to run on a particular set of labeled nodes. Uncomment the ‘label’ line and replace the label to match what is required in your particular setup.
Next up is the ‘options’ setting. This section configures a few global settings, including maximum runtime, number of old run logs to keep around, where the source should be checked out, etc. Add/change as desired.
The environment section dictates where things will be located. In this example file, there are two:
SOURCEDIR
specifies where in the workspace the git repository has been cloned. By default, Jenkins will extract into the workspace. That configuration isn’t desirable in this use case because it also needs to have a directory to store Yetus’ output…YETUS_PATCHDIR
is where that output will go. It needs to be relative to theWORKSPACE
for artifact archiving to be able to work correctly to grab Yetus’ logs.
In Jenkins Pipelines, the stages stanza tells Jenkins what it is supposed to execute for this pipeline. These stages can be as complex as needed. For this example, it only has one: run Apache Yetus.
The ‘apache-yetus’ stage is the meat of this pipeline. It starts by getting the Github credentials that should be used to use the Github API to both write commentary and get more details about pull requests. Be sure to change the ID to the one that you used previously.
After that, a relatively simple shell script is executed. It does a few housekeeping things:
- Forcibly change the directory to
WORKSPACE
- Forcibly set the
JENKINS_URL
environment variable to work around a Jenkins bug - Does some work to ensure the
YETUS_PATCHDIR
directory is cleaned out
From here on is where the fun stuff happens. The YETUS_ARGS
array contains all of the options that need to get passed to the ‘test-patch’ command:
--patch-dir
sets the output directory. This directory will get archived later.--basedir
sets the source directory so that the system knows what directory to process- The various
report
options configure up different output formats that are useful in a variety of contexts. - The
--github
options tell test-patch the username and password to use to talk to Github - The
--plugins
option configure which optional features to use. Here, it turns them all on (including GPL ones if that is an operational concern) - The
--build-url-artifacts
option helps test-patch tailor its output to match how Jenkins will present URLs for the archived output. - The
--empty-patch
option tells test-patch that if no PR was provided, run as though it was in ‘qbt — quality build tool’ mode by treating the entire source tree as a patch.
Then it executes test-patch.
After that stanza is a ‘post’ section. This section tells Jenkins to perform various tasks after the main work is finished. In this example it has:
- Archive the output directory as job artifacts
- Publish the HTML report on the job webpage to provide an easier to read a table of output
- Delete the workspace directory after completion. This function prevents space from being consumed on the slaves.
This file should now be committed as Jenkinsfile
to the root of the source tree of the repo in the master branch so that Jenkins will see it after the job is added.
Jenkins Job Configuration
Now that I have Jenkinsfile
, it is time to create the actual job. [In this example, I’m going to configure my personal Apache Zookeeper repository.]
Select New Item, give it a name, and then use Multibranch Pipeline:
Next, configure the Branch Source to be Github. The credentials used here should be ones that have access to the repository. Provide the owner of the repo and then select the repo to test. You will also probably want to change the Trust setting as appropriate for your project. The question mark on the right-hand side can provide some guidance:
Save the job. Congratulations, you should now have a working pipeline! Jenkins should be using the user configured to update the Github web hook and processing all of the existing branches and pull requests. It should also trigger a build for any of those that already have a the Jenkinsfile
present. All new branches pull requests that have the Jenkinsfile
should also start processing. Apache Yetus will process the content and post results on both the Github PR page and on the Jenkins build page.
Troubleshooting
Web hooks
If for whatever reason you cannot use a token that actually has permission to set the web hook, let the person who does have administrative control set it to be JENKINS_URL/github-webhook/
, where JENKINS_URL
is the base URL you use to get to Jenkins. (For example, https://builds.apache.org/github-webhook/). The web hook needs minimally pushes and pull request notifications. For more help, checkout out the Cloudbees website.
Limits
To ensure that our unit tests and the like don’t cause problems, Apache Yetus sets a relatively small process limit. For larger, complex projects, this may not be enough. To increase the upper bound on the number of processes, the --proclimit
option may be used. Add the following line to the YETUS_ARGS
sequence:
1 |
YETUS_ARGS+=("--proclimit=2000") |
Ignoring Test Plug-in Failures
Running Apache Yetus on existing source trees tends to highlight problems that may or may not have been previously known. For example, excessive whitespace may take a more organized effort to clean. In the meantime, you may not want to have the run fail but still generate a report. Ignoring results can be done by using the --tests-filter
option:
1 |
YETUS_ARGS+=("--tests-filter=whitespace") |
This way, tests don’t have to be disabled entirely.
Disabling Plug-ins
In some source trees, it may not be appropriate to run particular Apache Yetus plug-ins. The --plugins
option supports a basic logic operator to disable tests selectively while still running everything else:
1 |
YETUS_ARGS+=("--plugins=all,-authors") |
In this example, the ‘authors’ plugin that checks for the existence of ‘@authors’ lines has been entirely disabled, perhaps because our project wants to use them.
Other Build Tools
While this example used maven, Apache Yetus supports a wide variety of other build tools. In these examples, changing the --build-tool
parameter and adding any additional build tool specific parameters should be all that is required. For example, to use Apache Ant:
1 |
YETUS_ARGS+=("--build-tool=ant") |
alternatively, perhaps cmake:
1 |
YETUS_ARGS+=("--build-tool=cmake") |
Or, maybe none at all:
1 |
YETUS_ARGS+=("--build-tool=nobuild") |
Conclusion
This post covers some of the basics required to get a (relatively) simple project working. In a future post, I’ll cover how to import Apache Yetus into an existing Docker image, how to use test-patch to build the image on the fly, and a few more use cases.