Jenkinsfile Pipeline

Blog Logo

Christopher LaPointe

on
20 Dec 2017

read

Jenkins Declarative Pipeline

The Jenkinsfile (or rather, declarative pipelines in general) has been one of the best improvements to CI/CD in the past few years. I love Jenkins,
but for whatever reason, I always struggle to do anything with its pipelines beyond the most basic use-case.

I've spent way too long putting together my Jenkinsfile for ubsub.io, so I thought I'd share it, so that there is another
example out there for someone to look at. I've obfuscated some bits of it, but it will still be a valuable example.

I'll make one last note before we dive into the code: The way I currently have ubsub's source set up is not a best-practice. Everything is
lumped together under the same source tree, and I may split it up at some point, but this is working for now. If you're with a team or large
group, I'd highly recommend breaking up your different pieces into different repos rather than copying what I've done.

The Example

I've added a bunch of inline comments below. One thing to call out is my primary use-case for this builds many different components in a single pipeline,
some of which require different languages and platforms. This is reflected in my stages and sub-stages below.

Jenkinsfile

pipeline{agent{// My docker "agent" is on a VM because the host system isn't a new enough kernel to support dockerlabel'docker'}environment{// Some environment variables to be used laterARTIFACTPATH="output"OUTPUT="bundle.tar.gz"}stages{// Just a small stage to notify bitbucket that we're under waystage('Notify'){steps{bitbucketStatusNotify'INPROGRESS'}}// We could parallelize it, but I've chosen not to, mostly due to resource restrictions// The first build-pass will be a golang build environmentstage('Docker:Go'){agent{// Use golangdocker{image'golang:1.9.2'// Use the same node as the rest of the buildreuseNodetrue// Do go-platform stuff and put my app into the right directoryargs'-v $WORKSPACE/components/udpgo:/go/src/udpgo'}}steps{// While not technically necessary to have the "script" section here// it is more consistent with what I do belowscript{// You could split this up into multiple stages if you wanted tostage('Compile:UDP'){sh'cd $GOPATH/src/udpgo && go get -t'sh'cd $GOPATH/src/udpgo && go test'sh'cd $GOPATH/src/udpgo && go build'}}}}stage('Docker:Node'){agent{// This dockerfile is built from a custom `Dockerfile` in the directory `build/` (See below for its contents)dockerfile{dir'build/'// Use the same node as the rest of the buildreuseNodetrue}}steps{// Now, all the stages for this nodescript{stage('Node:Setup'){sh'npm install'sh'pip install --user -r requirements.txt'}stage('Install'){sh'npm install'}stage('Lint'){try{sh'eslint --quiet -f junit -o test-results.xml .'}finally{junitallowEmptyResults:true,testResults:'**/test-results.xml'}}stage('UI'){try{sh'npm run pm2:start:router'sleep10// Currently jenkins doesn't support `dir` in docker, so I cd before each commandsh'cd ui && npm run gulp'sh'cd ui && npm run webpack:prod'}finally{sh'npm run pm2:stop'}}stage('Router Tests'){sh'cd router && npm run test'}stage('Integration Tests'){try{sh'npm run pm2:start:router && npm run pm2:start:jsvm'sleep10// Wait for things to startsh'npm run test'}finally{sh'npm run pm2:stop'}}stage('Prune'){// Prune my modules before sending it off to prodsh'./each.sh npm prune --production'}}}}stage('Bundle'){steps{// Archive everything to be sent to the server latersh'rm -rf $ARTIFACTPATH'sh'mkdir -p $ARTIFACTPATH'// Use `.bundleignore` as a file similar to .gitignore so different components can exclude pieces from being sent to prodsh'tar czf $ARTIFACTPATH/$OUTPUT --exclude $ARTIFACTPATH --exclude-ignore .bundleignore .'archiveArtifacts"${env.ARTIFACTPATH}/*"}}}post{// Finally let bitbucket knowsuccess{bitbucketStatusNotify'SUCCESSFUL'}failure{bitbucketStatusNotify'FAILED'}always{// And cleanupdeleteDir()}}}