Table of contents
Introduction
In the last post, I introduced a Jenkinsfile that used Apache Yetus to do source tree analysis. However, that example ran the entire process in the Apache Yetus’ default container. For some projects that may not work, especially in consideration of any custom requirements. Plus, it didn’t produce an artifact which may also be required. In this post, I’ll cover some other techniques of integrating Apache Yetus into an existing workflow.Using a Container for an Existing Pipeline
Jenkins can run parts of the pipeline in different containers. Using that functionality, it’s possible to take advantage of Apache Yetus in an existing pipeline without a significant amount of work by just introducing an Apache Yetus to your existing pipeline. Here is an example that leverages the pre-built container image:
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 |
pipeline { agent { label 'foo||bar' } options { checkoutToSubdirectory('src') } environment { // must be relative to the workspace and match the above checkout SOURCEDIR='src' YETUS_PATCHDIR = 'out' } stages { ... more stages ... stage('Apache Yetus static analysis') { steps { script { docker.image('apache/yetus:0.10.0').inside { withCredentials([usernamePassword(credentialsId: 'yetus-github-account', passwordVariable: 'GITHUB_PASSWORD', usernameVariable: 'GITHUB_USER')]) { sh '''#!/usr/bin/env bash cd "${WORKSPACE}" # 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}") # location of source 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}") # disable compilation # YETUS_ARGS+=("--build-tool=nobuild") # 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}") # ... add any other YETUS_ARGS here ... # execute! test-patch "${YETUS_ARGS[@]}" ''' try { // Publish the HTML report so that it can be looked at // Has to be relative to WORKSPACE. archiveArtifacts "${env.YETUS_PATCHDIR}/**" } catch (Exception e) { echo 'Caught archive error: ' + e.toString() } try { publishHTML (target: [ allowMissing: true, keepAll: true, alwaysLinkToLastBuild: true, // Has to be relative to WORKSPACE reportDir: "${env.YETUS_PATCHDIR}", reportFiles: 'report.html', reportName: 'Yetus Report' ]) } catch (Exception e) { echo 'Caught html publish error: ' + e.toString() } } } } } } ... more stages ... } ... more stuff ... } |
${WORKSPACE}/src
, downloads the Apace Yetus 0.10.0 docker image, and then runs test-patch
against the source tree. It generates reports and such in ${WORKSPACE}/out
. That directory will be archived later on for further perusal. The HTML report is also published on the Jenkins page as well. If Apache Yetus cannot compile your source tree (e.g., missing resources), uncommenting the nobuild
option configures test-patch
to not trigger any known source tree building tools, generally only enabling static linters.
The most significant difference is that the only part of the pipeline that runs in the Apache Yetus container image is Apache Yetus itself. Other stages execute in either the Jenkins’ worker environment or, if defined with similar docker()
statements, in a container.
It is important to note that when running in this mode, Jenkins automatically populates some environment variables and volume mounts the entirety of the ${WORKSPACE}
directory in the container. Thus this pipeline still uses a modified directory structure to share data to the world outside of its container. Adding additional volumes and adding additional parameters (e.g., --patchdir
) to the test-patch
command line would allow one to use a different structure if changing the repository clone location is too painful.
Customizing the Build via Local Installation
For complex projects, it may be desirable to runtest-patch
in the same environment that the build uses typically, either on the build node or in a custom container. This type of setup requires getting a version of Apache Yetus and its dependencies installed in the appropriate environment.
First, let us get Apache Yetus installed. Officially, source archives are the official release mechanism of Apache Software Foundation projects. This type of distribution is less than ideal for our purposes. The Apache Yetus project also makes available a pre-built, binary package.
Installation of a single version as part of your regular Jenkins slave installation is reasonably trivial: grab the binary package from https://yetus.apache.org/downloads/, verify against the GPG key, and install.
Be aware that the URLs of versions change. Apache Software Foundation policy prevents projects from keeping every version installed on a mirror. So as versions get removed, the download link also changes from a local mirror to the central Apache archival server.
The Apache Yetus project provides some sample code here to download, verify, and install the latest released version in a directory of your choosing. [Users of this code should be aware that Apache Yetus doesn’t follow semantic versioning so that incompatibilities appear from time-to-time.]
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 |
pipeline { agent { docker { image "project/image:0.0.0" //label 'foo||bar' } } stages { ... stage ('install yetus') { steps { dir("${WORKSPACE}") { sh ''' # install the latest version of Apace Yetus in the yetus directory on the workspace ./yetus-dl.sh "${WORKSPACE}/${YETUS}" ''' } } } stage('Apache Yetus static analysis') { steps { script { withCredentials([usernamePassword(credentialsId: 'yetus-github-account', passwordVariable: 'GITHUB_PASSWORD', usernameVariable: 'GITHUB_USER')]) { sh '''#!/usr/bin/env bash cd "${WORKSPACE}" TESTPATCHBIN="${WORKSPACE}/${YETUS}/bin/test-patch" ... YETUS_ARGS configuration from above ... "${TESTPATCHBIN}" "${YETUS_ARGS[@]}" ... etc ... } |
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 |
... stuff ... environment { YETUS='yetus' // Branch or tag name. Yetus release tags are 'rel/X.Y.Z' YETUS_VERSION='rel/0.10.0' } stages { ... stage ('install yetus') { steps { dir("${WORKSPACE}/${YETUS}") { checkout([ $class: 'GitSCM', branches: [[name: "${env.YETUS_VERSION}"]], userRemoteConfigs: [[ url: 'https://github.com/apache/yetus.git']]] ) } } } stage('Apache Yetus static analysis') { steps { script { withCredentials([usernamePassword(credentialsId: 'yetus-github-account', passwordVariable: 'GITHUB_PASSWORD', usernameVariable: 'GITHUB_USER')]) { sh '''#!/usr/bin/env bash cd "${WORKSPACE}" TESTPATCHBIN="${WORKSPACE}/${YETUS}/precommit/src/main/shell/test-patch.sh" ... YETUS_ARGS configuration from above ... "${TESTPATCHBIN}" "${YETUS_ARGS[@]}" ... etc ... } |
git clone
a known release of the Apache Yetus source tree and run from there. This method allows the test-patch
to run in the same environment as Jenkins’s worker node without having to worry about changing URLs or installing a script in your source tree.
Custom Docker Environment
The other big benefit of runningtest-patch
from outside the container is that it is possible to run it inside a built-at-runtime container. This method is exactly how Apache Yetus runs against itself. Other examples include Apache Hadoop and Apache HBase.
In all of these examples, you can see that the --docker
parameter. This option tells test-patch
that it should run the test in a container. The --dockerfile
option points to the location of a Dockerfile. If provided, test-patch
builds that content as the container and then runs inside of it. If the --dockerfile
option is not provided, then test-patch
will use the same image as apache/yetus-base either by downloading it or by building it from scratch.
Another option of interest may be the --docker-cache-from
option. This option is the same as using the docker build --cache-from
option to speed up builds. Apache Yetus downloads the images passed and then use those to build the run-specific container image. Using this method, building the Dockerfile only when there are changes makes the whole process significantly faster.
It is important to note that if a custom container must have all of the executables and support libraries for all of the plug-ins that you want to run with test-patch
. A comprehensive list is maintained as part of Apache Yetus’ documentation. Where the necessary pre-requisites are not available, test-patch
will warn that the feature is not available and move on.