Skip to content

Clean code

Valentina-Camelia Bojan edited this page Nov 11, 2019 · 10 revisions

The Jenkinsfile for a a real application can become really complex. It will contain logic for multiple stages and steps, integrations with different external tools, trigger for extra jobs and so on. This is an example of how a not so complex Jenkinsfile can look like.

Even if it's infrastructure code it should be treated as normal production code. Same clean code principles apply when writing a Jenkinsfile too.

This being said, we should apply all necessary practices to keep our Jenkinsfile as clean (simple and short) as possible.

Extract functions

We already have 2 functions defined there. Let's start by extracting some more functions, each one of them corresponding to logic done inside the stages.

You can easily create 3 functions for the Build, Test, and Deploy stages as following:

void buildApplication() {
    withMaven(maven: MAVEN_INSTALLATION, mavenSettingsConfig: MAVEN_SETTINGS_CONFIG) {
        sh 'mvn -DskipTests clean package'
    }
}

void runAllTests() {
    withMaven(maven: MAVEN_INSTALLATION, mavenSettingsConfig: MAVEN_SETTINGS_CONFIG) {
        sh 'mvn test'
    }
}

void deployApplication() {
    echo "Deploying application..."
}

You can do the same with the logic that sends Slack notifications in different stages of the build:

void sendSlackNotificationForRegularStage() {
    statusComment = "[${env.JOB_NAME}] <${env.BUILD_URL}|#${env.BUILD_NUMBER}> ${env.STAGE_NAME} stage completed succesfully for ${env.GIT_BRANCH}"
    slackSend color: '#0000ff', message: statusComment
}

void sendSlackNotificationForApprovedStage(String responder) {
    statusComment = "[${env.JOB_NAME}] <${env.BUILD_URL}|#${env.BUILD_NUMBER}> ${env.STAGE_NAME} stage was approved by ${responder} for ${env.GIT_BRANCH}"
    slackSend color: '#0000ff', message: statusComment
}

void sendSlackNotificationForSuccessfulBuild() {
    statusComment = "[${env.JOB_NAME}] <${env.BUILD_URL}|#${env.BUILD_NUMBER}> completed succesfully for ${env.GIT_BRANCH} :tada:"
    slackSend color: 'good', message: statusComment
}

void sendSlackNotificationForFailedBuild() {
    statusComment = getTestResultsMessage()
    slackSend color: 'danger', message: statusComment
}

void sendSlackNotificationForAbortedBuild() {
    statusComment = "[${env.JOB_NAME}] <${env.BUILD_URL}|#${env.BUILD_NUMBER}> for ${env.GIT_BRANCH} was aborted by ${getBuildUser()}"
    slackSend message: statusComment
}

Now, just call the above extracted functions:

pipeline {
    agent any

    environment {
        MAVEN_INSTALLATION = 'maven-installation'
        MAVEN_SETTINGS_CONFIG = 'maven-settings.xml'
    }

    stages {
        stage('Build') {
            steps {
                buildApplication()
                sendSlackNotificationForRegularStage()
            }
        }

        stage('Test') {
            steps {
                runAllTests()
                sendSlackNotificationForRegularStage()
            }
        }

        stage('Deploy') {
            input {
                message 'Do you want to deploy?'
                submitterParameter 'responder'
            }
            steps {
                deployApplication()
                sendSlackNotificationForApprovedStage(responder)
            }
        }
    }

    post {
        success {
            sendSlackNotificationForSuccessfulBuild()
        }
        failure {
            sendSlackNotificationForFailedBuild()
        }
        aborted {
            sendSlackNotificationForAbortedBuild()
        }
    }
}

Notice how easily you can follow and understand what your pipeline does. Here you can find the complete Jenkinsfile after the above refactoring.

Move extracted functions to dedicated groovy files

But not only the pipeline should be short and simple, but also the Jenkinsfile should be kept as clean as possible. Currently it contains a bunch of functions we previously extracted. They are useful for the pipeline logic, but they can be grouped by functionality and moved into dedicated groovy files. Let's see how.

You can start with the logic related to Build, Test and Deploy stages and create a stages.groovy file, next to the Jennkisnfile, as here.

You can do the same withe the functions for Slack notifications and create a slack-notifier.groovy file with them, as here.

You now can load the two files using the following new stage:

stage('Init') {
    steps {
        script {
            stagesLib = load "stages.groovy"
            slackNotifier = load "slack-notifier.groovy"
        }
    }
}

To call a function defined in stages.groovy, use the defined variable as following:

stagesLib.buildApplication()

Finally, your Jenkinsfile will look like this.