Article Image
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 docker
        label 'docker'
    }
    environment {
        // Some environment variables to be used later
        ARTIFACTPATH = "output"
        OUTPUT = "bundle.tar.gz"
    }
    stages {
        // Just a small stage to notify bitbucket that we're under way
        stage('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 environment
        stage('Docker:Go') {
            agent {
                // Use golang
                docker {
                    image 'golang:1.9.2'
                    // Use the same node as the rest of the build
                    reuseNode true
                    // Do go-platform stuff and put my app into the right directory
                    args '-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 below
                script {
                    // You could split this up into multiple stages if you wanted to
                    stage('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 build
                    reuseNode true
                }
            }
            steps {
                // Now, all the stages for this node
                script {
                    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 {
                            junit allowEmptyResults: true, testResults: '**/test-results.xml'
                        }
                    }
                    stage('UI') {
                        try {
                            sh 'npm run pm2:start:router'
                            sleep 10
                            // Currently jenkins doesn't support `dir` in docker, so I cd before each command
                            sh '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'
                            sleep 10 // Wait for things to start
                            sh 'npm run test'
                        } finally {
                            sh 'npm run pm2:stop'
                        }
                    }
                    stage('Prune') {
                        // Prune my modules before sending it off to prod
                        sh './each.sh npm prune --production'
                    }
                }
            }
        }

        stage('Bundle') {
            steps {
                // Archive everything to be sent to the server later
                sh '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 prod
                sh 'tar czf $ARTIFACTPATH/$OUTPUT --exclude $ARTIFACTPATH --exclude-ignore .bundleignore .'
                archiveArtifacts "${env.ARTIFACTPATH}/*"
            }
        }
    }

    post {
        // Finally let bitbucket know
        success {
            bitbucketStatusNotify 'SUCCESSFUL'
        }
        failure {
            bitbucketStatusNotify 'FAILED'
        }
        always {
            // And cleanup
            deleteDir()
        }
    }
}
Blog Logo

Christopher LaPointe


Published

Interested in Related Posts from this Site?

Routing Your Events with UbSub

September 14, 2017: PubSub & The Events Problem After I started to play more and more with IoT...

Let's Encrypt

April 18, 2016: Let's Encrypt About 6 months ago, Lets Encrypt was brought to my attention as I...

SshSysMon

April 01, 2016: SshSysMon Ssh System Monitor (SshSysMon) is a system monitoring and alerting tool that operates purely...

Linux Router Home Network

February 21, 2018: # Linux Router Home Network I have a fairly complicated network at home... at least...

Image

Chris LaPointe

Another site of Code

Back to Overview