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.
Join the DZone community and get the full member experience.
Join For FreeJenkins 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.
Step 2: Enter Jenkins job name, choose the style as Pipeline, and click OK.
Step 3: Scroll down to the Pipeline section and copy-paste your first Declarative style Pipeline code from below to the script textbox.
Step 4: Click on the Save button and click on Build Now from the left side menu.
We can see the build running stage by stage in Stage View.
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
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
any
– Run Job or Stage on any available agent.pipeline { agent any }
none
– Don’t allocate any agent globally for the pipeline. Every stage should specify their own agent to run.pipeline { agent none }
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 aslinux-machine
.pipeline { agent { label 'linux-machine' } }
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}"
}
}
}
}
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.
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.
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"
}
}
}
}
}
}
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.
We used build with parameters to pass params at the time of running the job.
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.
Step 3: Click on Configure System under System Configuration.
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:
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}"
}
}
}
}
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}"
}
}
}
}
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'
}
}
}
}
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.
Step 3: Click on Manage Credentials under Security.
Step 4: Click on the Global hyperlink.
Step 5: Click on Add Credentials from the left side menu.
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
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 _USR
to the variable we assigned credentials()
output and append _PSW
to get the password.
Let's load the credential at MY_CRED
variable 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"
}
}
}
}
Interestingly, Jenkins prints the username and password as **** to avoid accidental leakage of credentials.
when
Acts like if condition to decide whether to run the particular stage or not. It's an optional block.
- when block
pipeline { agent any stages { stage('build') { when { branch 'dev' } steps { echo "Working on dev branch" } } } }
- when block using Groovy expression:
pipeline { agent any stages { stage('build') { when { expression { return env.BRANCH_NAME == 'dev'; } } steps { echo "Working on dev branch" } } } }
- 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' } } } }
- 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' } } } }
- 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' } } } }
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.
Step 3: Click on Global Tools Configuration under System Configuration.
Step 4: Scroll down to the Maven section and click on Maven Installations.
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
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.
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"
}
}
}
}
Sample can be found here at GitHub.
Console Output
Run the job and we should see the mvn -version
command response in console output:
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 statuschanged
– 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 timeaborted
– if the build is aborted in the middle of the run, usually due to manual stopping of the build runfailure
– if the build status is failuresuccess
– if the build ran successfullyunstable
– 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"
}
}
}
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')
}
}
}
}
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'
}
}
}
}
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!
Published at DZone with permission of Praveen Mishra, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments