8

Comprehensive Guide to Jenkins Declarative Pipeline [With Examples]

 2 years ago
source link: https://dzone.com/articles/comprehensive-guide-to-jenkins-declarative-pipelin
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Comprehensive Guide to Jenkins Declarative Pipeline [With Examples]

With Jenkins CI/CD’s latest version, creating a Jenkins Declarative Pipeline is no longer code-intensive or daunting.

Jenkins Pipeline is an automation solution that lets you create simple or complex (template) pipelines via the DSL used in each pipeline. Jenkins provides two ways of developing a pipeline — scripted and declarative. Traditionally, Jenkins jobs were created using Jenkins UI called freestyle jobs. In Jenkins 2.0, Jenkins introduced a new way to create jobs using the technique called pipeline as code. In the pipeline as code technique, jobs are created using a script file that contains the steps to be executed by the job. In Jenkins, that scripted file is called a Jenkinsfile. In this Jenkins tutorial, we will take a deep dive into Jenkins Declarative Pipeline with the help of Jenkins Declarative Pipeline examples.

Let’s get started with the basics.

What Is a Jenkinsfile?

A Jenkinsfile is just a text file, usually checked in along with the project’s source code in Git repo. Ideally, every application will have its own Jenkinsfile.

A Jenkinsfile can be written in two aspects — scripted pipeline syntax and declarative pipeline syntax.

What Is Jenkins Scripted Pipeline?

Jenkins pipelines are traditionally written as scripted pipelines. Ideally, the scripted pipeline is stored in the Jenkins webUI as a Jenkins file. The end-to-end scripted pipeline script is written in Groovy.

  • It requires knowledge of Groovy programming as a prerequisite.
  • The Jenkinsfile starts with the word node.
  • Can contain standard programming constructs like if-else block, try-catch block, and so on.

Sample Scripted Pipeline

node {
    stage('Stage 1') {
        echo 'hello'
    }
}

What Is Jenkins Declarative Pipeline?

The Declarative Pipeline subsystem in Jenkins Pipeline is relatively new, and provides a simplified, opinionated syntax on top of the Pipeline subsystems.

  • The latest addition in Jenkins pipeline job creation technique.
  • Jenkins declarative pipeline needs to use the predefined constructs to create pipelines. Hence, it is not flexible as a scripted pipeline.
  • A Jenkinsfile starts with the word pipeline.

Jenkins declarative pipelines should be the preferred way to create a Jenkins job, as they offer a rich set of features, come with less learning curve, and there are no prerequisites to learn a programming language like Groovy just for the sake of writing pipeline code.

We can also validate the syntax of the declarative pipeline code before running the job. It helps to avoid a lot of runtime issues with the build script.

Our First Declarative Pipeline

pipeline {
    agent any
    stages {
        stage('Welcome Step') {
            steps { 
                echo 'Welcome to LambdaTest'
            }
        }
    }
}

We recommend VS Code IDE for writing Jenkins pipeline scripts, especially when creating Jenkins Declarative pipeline examples.

Running Your First Declarative Pipeline

Now that you are well-acquainted with the Jenkins pipeline’s basics, it’s time to dive deeper. In this section, we will learn how to run a Jenkins declarative pipeline.

Let us run our Jenkins declarative pipeline step by step.

Step 1: Open Jenkins home page (http://localhost:8080 in local) & click on New Item from the left side menu.

15842295-1650982364356.png

Step 2: Enter Jenkins job name, choose the style as Pipeline, and click OK

15842296-1650982377579.png

Step 3: Scroll down to the Pipeline section and copy-paste your first Declarative style Pipeline code from below to the script textbox. 

15842297-1650982393370.png

Step 4: Click on the Save button and click on Build Now from the left side menu. 

15842298-1650982407488.png

We can see the build running stage by stage in Stage View. 

15842299-1650982420379.png

Step 5: To check logs from Build Job, click on any stage and click on the check logs button. Or you can use the Console Output from the left side menu to see the logs for the build.

Console Output

15842300-1650982435632.png

Jenkins Declarative Pipeline Syntax

In this section, we will look at the most commonly used Jenkins declarative pipeline examples or syntax. Typically, declarative pipelines contain one or more declarative steps or directives, as explained below.

pipeline

Entire Declarative pipeline script should be written inside the pipeline block. It’s a mandatory block.

pipeline {
}

agent

Specify where the Jenkins build job should run. agent can be at pipeline level or stage level. It’s mandatory to define an agent.

Possible Values

  1. any– Run Job or Stage on any available agent. 
    pipeline {
         agent any
    }
  2. none– Don’t allocate any agent globally for the pipeline. Every stage should specify their own agent to run.
    pipeline {
         agent none
    }	
  3. label – Run the job in agent which matches the label given here. Remember Jenkins CI/CD can work on Master/Agent architecture. Master nodes can delegate the jobs to run in Agent nodes. Nodes on creation given a name and label to identify them later, e.g., all Linux nodes can be labeled as linux-machine.
    pipeline {
           agent {
               label 'linux-machine'
           }
    }
  4. docker  Run the job in a given Docker container.

stages

The stages block constitutes different executable stage blocks. At least one stage block is mandatory inside the stages block.

pipeline {
       agent {
           label 'linux-machine'
       }
     stages {
     }
}

stage

The stage block contains the actual execution steps. The stage block has to be defined within the stages block. It’s mandatory to have at least one stage block inside the stages block. Also, it's mandatory to name each stage block. This name will be shown in the Stage View after we run the job.

In below example, the stage is named as “build step”:

pipeline {
       agent {
           label 'linux-machine'
       }
     stages {
         stage('build step') {
         }
     }
}

A sample can be found here at GitHub.

steps

The steps block contains the actual build step. It’s mandatory to have at least one step block inside a stage block.

Depending on the Agent’s operating system (where the Jenkins job runs), we can use shell, bat, etc., inside the steps command.

pipeline {
     agent any
     stages {
         stage('build step') {
              steps {
                 echo "Build stage is running"
              }
         }
     }
}

Scripted Pipeline in Jenkins job

Sample can be found here at GitHub.

parameters

The parameters directive provides a way for Jenkins jobs to interact with Jenkins CI/CD users during the running of the build job.

A parameter can be of the following types: string, text, booleanParam, choice, and password.

string – Accepts a value of String type from Jenkins user.

E.g.
string(name: ‘NAME’, description: ‘Please tell me your name?’)

text – Accepts multi line value from Jenkins user
E.g.
text(name: ‘DESC’, description: ‘Describe about the job details’)

booleanParam – Accepts a true/false value from Jenkins user
E.g.
booleanParam(name: ‘SKIP_TEST’, description: ‘Want to skip running Test cases?’)

choice – Jenkins user can choose one among the choices of value provided
E.g.
choice(name: ‘BRANCH’, choices: [‘Master’, ‘Dev’], description: ‘Choose the branch’)

password – Accepts a secret like password from Jenkins user
E.g.
password(name: ‘SONAR_SERVER_PWD’, description: ‘Enter SONAR password’)

Let’s look at a sample on how to use the parameters directive:

pipeline {
    agent any
    parameters {
        string(name: 'NAME', description: 'Please tell me your name?')

        text(name: 'DESC', description: 'Describe about the job details')

        booleanParam(name: 'SKIP_TEST', description: 'Want to skip running Test cases?')

        choice(name: 'BRANCH', choices: ['Master', 'Dev'], description: 'Choose branch')

        password(name: 'SONAR_SERVER_PWD', description: 'Enter SONAR password')
    }
    stages {
        stage('Printing Parameters') {
            steps {
                echo "Hello ${params.NAME}"

                echo "Job Details: ${params.DESC}"

                echo "Skip Running Test case ?: ${params.SKIP_TEST}"

                echo "Branch Choice: ${params.BRANCH}"

                echo "SONAR Password: ${params.SONAR_SERVER_PWD}"
            }
        }
    }
}
15842309-1650982648547.png

If a parameters directive is used in the pipeline, Jenkins CI/CD can sense that it needs to accept the user’s input while running the job. Hence, Jenkins will change the Build now link in the left side menu to a Build with parameters link. 

15842310-1650982677428.png

When we click on the Build with Parameters link, Jenkins CI/CD will let us pass values for the parameters we configured in the declarative pipeline. 

15842311-1650982692332.png

Once we enter each parameter’s values, we can hit the Build button to run the job.

script

The script block helps us run Groovy code inside the Jenkins declarative pipeline.

pipeline {
    agent any
    parameters {
        string(name: 'NAME', description: 'Please tell me your name')
        choice(name: 'GENDER', choices: ['Male', 'Female'], description: 'Choose Gender')
    }
    stages {
        stage('Printing name') {
            steps {
                script {
                    def name = "${params.NAME}"
                    def gender = "${params.GENDER}"
                    if(gender == "Male") {
                        echo "Mr. $name"    
                    } else {
                        echo "Mrs. $name"
                    }
                }
            }
        }
   }
}
15842312-1650982719488.png

The script block is wrapped inside the steps block. In the above example, we are printing the name passed as parameter prefixed with Mr. or Mrs. based on the gender chosen. 

15842314-1650982736335.png

We used build with parameters to pass params at the time of running the job. 

15842315-1650982750140.png

Environment

Key value pairs which help pass values to a job during job runtime from outside of the Jenkinsfile. It’s one way of externalizing configuration.

Example: Usually, Jenkins jobs will run in a separate server. We may not be sure where the installation path of Java or JMeter is on that server. Hence, these are ideal candidates to be configured in environment variables and passed during the job run.

Method 1: Configure Environment Variable in Jenkins CI/CD Portal

Step 1: Open Jenkins Server URL (http://localhost:8080).

Step 2: Click on Manage Jenkins from the left sidebar menu.

15842319-1650982774811.png

Step 3: Click on Configure System under System Configuration. 

15842320-1650982799309.png

Step 4: Scroll down to the Global Properties section. This is where we will add our environment variables.

Step 5: Click on the Add button under Environment Variables and enter the key and value.

We have added the Java installation path under the variable name JAVA_INSTALLATION_PATH:

15842321-1650982811426.png

Step 6: Click on Save button.

Refer Environment Variable in Jenkinsfile
We can refer to the environment variables in a declarative pipeline using the ${} syntax.

pipeline {
    agent any
    stages {
        stage('Initialization') {
            steps {
                echo "${JAVA_INSTALLATION_PATH}"
            }
        }
   }
}
15842322-1650982840427.png

Method 2: Creating and Referring Environment Variable in Jenkinsfile

We can create key value pairs of environment variables under environment block. It’s an optional block under pipeline.

pipeline {
    agent any
    environment { 
        DEPLOY_TO = 'production'
    }
    stages {
        stage('Initialization') {
            steps {
                echo "${DEPLOY_TO}"
            }
        }
    }
	}
15842327-1650982882427.png

Method 3: Initialize Environment Variables Using sh scripts in Jenkinsfile

Let’s say we need the timestamp when the job gets run for logging purposes. We can create an environment variable which can hold the timestamp. But how to initialize it?

We can use the shell script to fetch the current timestamp and assign it to the environment variable.

For example, the following command will print the date and time in shell:

>  date '+%A %W %Y %X' 
Tuesday 03 2021 22:03:31

Let's see how to use the above command to initialize the environment variable in Jenkinsfile. 

pipeline {
    agent any
    stages {
        stage('Initialization') {
            environment { 
                   JOB_TIME = sh (returnStdout: true, script: "date '+%A %W %Y %X'").trim()
            }
            steps {
                sh 'echo $JOB_TIME'
            }
        }
    }
}
15842328-1650982920480.png

returnStdout: true makes sh step returning the output of the command so you can assign it to a variable.

Sample can be found here at GitHub.

Load Credentials via Environment Block

credentials() is a special method that can be used inside the environment block. This method helps loading the credentials defined at Jenkins configuration.

Let's see it with an example. First, let's configure credentials in Jenkins CI/CD.

Step 1: Open Jenkins Server URL (http://localhost:8080)

Step 2: Click on Manage Jenkins from the left sidebar menu.

15842329-1650982949225.png

Step 3: Click on Manage Credentials under Security

15842330-1650982967086.png

Step 4: Click on the Global hyperlink. 

15842332-1650982985716.png

Step 5: Click on Add Credentials from the left side menu. 

15842333-1650983001588.png

For demonstration, let me use the following values:

  • Kind – Username with password
  • Scope – Global
  • Username – admin
  • Password – root123
  • ID – MY_SECRET
  • Description – Secret to access server files
15842334-1650983020356.png

Step 6: Click the OK button. Now our credentials are configured.

Referring Credential in Jenkinsfile

We can use the special method called credentials() to load credentials we configured. We need to use the ID we used while configuring credentials to load a particular credential.

Example: credentials(‘MY_SECRET’)

To refer to the username, append _USRto the variable we assigned credentials() output and append _PSWto get the password.

Let's load the credential at MY_CREDvariable using the credentials(‘MY_SECRET’) method. We need to append _USR ($MY_CRED_USR) and _PSW ($MY_CRED_PSW) to MY_CRED variable to get the username and password.

pipeline {
    agent any
  environment { 
   	MY_CRED = credentials('MY_SECRET') 
    }
    stages {
        stage('Load Credentials') {
            steps {
                echo "Username is $MY_CRED_USR"
                echo "Password is $MY_CRED_PSW"
            }
        }
    }
}
15842335-1650983043188.png

Interestingly, Jenkins prints the username and password as **** to avoid accidental leakage of credentials. 

15842336-1650983065383.png

when

Acts like if condition to decide whether to run the particular stage or not. It's an optional block.

  1. when block 
    pipeline {
         agent any
         stages {
             stage('build') {
                  when {
                      branch 'dev'             
                  }
                  steps {
                     echo "Working on dev branch"
                  }
             }
         }
    }
    15842337-1650983148900.png
  2. when block using Groovy expression:
    pipeline {
         agent any
         stages {
             stage('build') {
                  when {
                      expression {
                         return env.BRANCH_NAME == 'dev';
                      }             
                  }
                  steps {
                     echo "Working on dev branch"
                  }
             }
         }
    }
    15842338-1650983170884.png
  3. when block with environment variables:
    pipeline {
        agent any
        environment { 
            DEPLOY_TO = 'production'
        }
        stages {
            stage('Welcome Step') {
                	when { 
        environment name: 'DEPLOY_TO', value: 'production'
    }
                steps { 
                    echo 'Welcome to LambdaTest'
                }
            }
        }
    }
    15842339-1650983201723.png
  4. when block with multiple conditions and all conditions should be satisfied:
    pipeline {
        agent any
        environment { 
            DEPLOY_TO = 'production'
        }
        stages {
            stage('Welcome Step') {
                when { 
                    allOf { 
                        branch 'master'; 
                        environment name: 'DEPLOY_TO', value: 'production'
                    } 
                }
                steps { 
                    echo 'Welcome to LambdaTest'
                }
            }
        }
    }
    15842340-1650983227025.png
  5. when block with multiple conditions and any of the given conditions should be satisfied:
    pipeline {
        agent any
        environment { 
            DEPLOY_TO = 'production'
        }
        stages {
            stage('Welcome Step') {
                when { 
                    anyOf { 
                        branch 'master';
                        branch 'staging' 
                    } 
                }
                steps { 
                    echo 'Welcome to LambdaTest'
                }
            }
        }
    }
    15842341-1650983249577.png

Sample can be found here at GitHub.

Tools

This block lets us add pre-configured tools like Maven or Java to our job’s path. It’s an optional block.

To use any tool, they have to be pre-configured under the Global Tools Configuration section. For this example, let’s see how to configure Maven. 

Step 1: Open Jenkins Server URL (http://localhost:8080).

Step 2: Click on Manage Jenkins from the left sidebar menu.

15842342-1650983272784.png

Step 3: Click on Global Tools Configuration under System Configuration. 

15842343-1650983285513.png

Step 4: Scroll down to the Maven section and click on Maven Installations

15842373-1650985757420.png

Step 5: Click on the Add Maven button. Enter the following values in the configuration.

  • Name – Maven 3.5.0
  • MAVEN_HOME – Enter the maven installation path in local
15842374-1650985776001.png

Alternatively we can download Maven from the Internet instead of pointing to the local installation path by enabling the Install Automatically checkbox.

Step 6: Click on Add Installer button and choose Install from Apache.

15842377-1650985803799.png

Step 7: Choose the Maven version to download. We have chosen the Maven version 3.6.3.

Step 8: Click on the Save button.

Refer to Maven tool in Pipeline in tools block with the name we used while configuring in Global Tools Configuration (MAVEN_PATH in this example).

pipeline {
    agent any
    tools {
        maven 'MAVEN_PATH' 
    }
    stages {
         stage('Load Tools') {
              steps {
                 sh "mvn -version"
              }
         }
     }
}
15842384-1650986428401.png

Sample can be found here at GitHub.

Console Output

Run the job and we should see the mvn -version command response in console output:

15842385-1650986456868.png

Similarly we can also configure and use tools like Java, Gradle, Git and so on from the tools block in pipeline code.

parallel

parallel blocks allow us to run multiple stage blocks concurrently. It’s ideal to parallelize stages which can run independently. We can define agents for each stage within a parallel block, so each stage will run on its own agent.

For example, when we want to run a piece of script in Windows agent and another script in Linux agent as part of the build, we can make them run concurrently using parallel blocks.

pipeline {
    agent any
    stages {
           stage('Parallel Stage') {
              parallel {
                stage('windows script') {
                    agent {
                        label "windows"
                    }
                    steps {
                        	echo "Running in windows agent"
		bat 'echo %PATH%'
                    }
                }
                stage('linux script') {
                    agent {
                        label "linux"
                    }
                    steps {
                       sh "Running in Linux agent"
                    }
                }
             }
        }
    }
}

Sample can be found here at GitHub.

post

The post block contains additional actions to be performed upon completion of the pipeline execution. It can contain one or many of the following conditional blocks: always, changed, aborted, failure, success, unstable, and so on.

Post Block Conditions

always – run this post block irrespective of the pipeline execution status
changed – run this post block only if the pipeline’s execution status is different from the previous build run, e.g., if the build failed at an earlier run and ran successfully this time
aborted – if the build is aborted in the middle of the run, usually due to manual stopping of the build run
failure – if the build status is failure
success – if the build ran successfully
unstable – build is successful but not healthy, e.g., on a particular build run, if test cases ran successfully, but test coverage percentage is less than expected, then the build can be marked as unstable

For example:

pipeline {
     agent any
     stages {
         stage('build step') {
              steps {
                 echo "Build stage is running"
              }
         }
     }
     post {
         always {
             echo "You can always see me"
         }
         success {
              echo "I am running because the job ran successfully"
         }
         unstable {
              echo "Gear up ! The build is unstable. Try fix it"
         }
         failure {
             echo "OMG ! The build failed"
         }
     }
}
15842396-1650987882740.png

Sample can be found here at GitHub.

Marking Build as Unstable

There are scenarios where we don’t want to mark build as failure, but want to mark build as unstable.

For example, when test case coverage doesn’t cross the threshold we expect, we can mark the build as UNSTABLE.

pipeline {
    agent any
    tools {
        maven 'MAVEN_PATH'
        jdk 'jdk8'
    }
    stages {
        stage("Tools initialization") {
            steps {
                sh "mvn --version"
                sh "java -version"
            }
        }
        stage("Checkout Code") {
            steps {
                git branch: 'master',
                url: "https://github.com/iamvickyav/spring-boot-data-H2-embedded.git"
            }
        }
        stage("Building Application") {
            steps {
               sh "mvn clean package"
            }
        }
        stage("Code coverage") {
           steps {
               jacoco(
                    execPattern: '**/target/**.exec',
                    classPattern: '**/target/classes',
                    sourcePattern: '**/src',
                    inclusionPattern: 'com/iamvickyav/**',
                    changeBuildStatus: true,
                    minimumInstructionCoverage: '30',
                    maximumInstructionCoverage: '80')
               }
           }
        }
 }
15842397-1650987914105.png

Sample can be found here at GitHub.

jacoco() is a special method which helps us configure Jacoco reports including the minimum and maximum coverage threshold. In the above example, min coverage (minimumInstructionCoverage) check is set as 30 and max coverage (maximumInstructionCoverage) check is set as 80.

So, if the code coverage is:

Less than 30 – Build will be marked as Failure.
Between 30 & 80 – Build will be marked as Unstable.
Above 80 – Build will be marked as Success.

triggers

Instead of triggering the build manually, we can configure the build to run in certain time intervals using the triggers block. We can use the special method called cron() within the triggers block to configure the build schedule.

Understanding Cron

Cron configuration contains five fields representing minutes (0-59), hours (0-23), day of the month (1-31), month (1-12), day of the week (0-7).

For example:

Every 15 minutes – H/15 * * * *
Every 15 minutes but only between Monday and Friday – H/15 * * * 1-5

pipeline {
    agent any
    triggers {
        cron('H/15 * * * *')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}
15842398-1650987941728.png

Sample can be found here at GitHub.

Conclusion

In this article, we have done an in-depth dive into Jenkins Declarative Pipeline examples and their usage. Instead of configuring build steps using UI in a remote Jenkins portal, we would always recommend you to create a Jenkinsfile with declarative pipeline syntax. A Jenkinsfile, as part of the application’s source code, will provide more control over CI/CD steps to developers. That’s the best way to make the most of Jenkins CI/CD and all the features it has to offer.

Happy building and testing!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK