As Jenkins has evolved, the Declarative Pipeline syntax has become the preferred way of defining Continuous Integration (CI) and Continuous Delivery (CD) pipelines. While the Scripted Pipeline provides more flexibility due to its Groovy-based nature, Declarative Pipelines offer simplicity, structure, and better error handling.
If you’re using a Scripted Pipeline and want to leverage the benefits of a Declarative Pipeline, this guide will walk you through how to convert an existing Scripted Pipeline into a Declarative one. We will also discuss best practices, and show examples of how common features in Scripted pipelines translate into Declarative syntax.
1. Overview of Scripted vs Declarative Pipelines
Before we dive into the conversion, let’s review the key differences between Scripted and Declarative Pipelines:
- Scripted Pipelines: Built on Groovy syntax, these pipelines provide full control and flexibility, but they require more in-depth coding knowledge. They allow users to write highly customized workflows but can be harder to read, debug, and maintain.
- Declarative Pipelines: Introduced to simplify pipeline creation, they have a clear and structured format. Declarative pipelines limit what you can do compared to Scripted pipelines but ensure consistency, readability, and better error handling. The pipeline { } block is mandatory, and stages, steps, and post-actions are explicitly defined.
Here’s an example of a basic Scripted pipeline:
node {
stage('Build') {
echo 'Building...'
}
stage('Test') {
echo 'Testing...'
}
stage('Deploy') {
echo 'Deploying...'
}
}
In contrast, here’s a basic Declarative pipeline:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
stage('Test') {
steps {
echo 'Testing...'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
}
}
}
}
As you can see, the Declarative syntax provides a cleaner, more readable structure.
2. Steps for Converting a Scripted Pipeline to a Declarative Pipeline
Let’s walk through the process of converting a Scripted pipeline into a Declarative one, step by step.
2.1 Identify the Pipeline Structure
The first step is to understand the overall structure of the Scripted pipeline. In Scripted pipelines, the node { }
block is used to define where the pipeline will run. Stages, steps, and actions are defined inside this block.
In Declarative pipelines, the overall structure is predefined. The pipeline must always include:
- A pipeline block
- An agent (which specifies where to run the pipeline)
- Stages with steps
- Post actions (for cleanup or notifications)
2.2 Define the Agent
In Declarative pipelines, the agent is required and specifies where the pipeline runs. It can be defined globally for the entire pipeline or locally for each stage.
Here’s how to define the agent in Declarative syntax:
pipeline {
agent any // Runs on any available agent
}
Alternatively, you can specify a particular label:
pipeline {
agent {
label 'my-agent-label'
}
}
In a Scripted pipeline, the equivalent would be defining the node:
node('my-agent-label') {
// pipeline steps
}
2.3 Convert Stages
The stages in a Scripted pipeline are defined inside the node { }
block. In Declarative pipelines, stages are organized inside a dedicated stages { }
block.
Here’s an example of converting Scripted stages into Declarative stages:
Scripted Pipeline (Stages):
node {
stage('Build') {
echo 'Building...'
}
stage('Test') {
echo 'Testing...'
}
stage('Deploy') {
echo 'Deploying...'
}
}
Declarative Pipeline (Stages):
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
stage('Test') {
steps {
echo 'Testing...'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
}
}
}
}
2.4 Translate Steps
Each stage in both Scripted and Declarative pipelines contains steps. However, the syntax in Declarative pipelines is more structured.
In a Scripted pipeline, steps are defined within a stage { }
block. In Declarative pipelines, the steps must be enclosed within a steps { }
block.
For example, translating steps like this:
Scripted Pipeline:
stage('Build') {
echo 'Building...'
}
Declarative Pipeline:
stage('Build') {
steps {
echo 'Building...'
}
}
2.5 Handle Post Actions
Post-build actions such as sending notifications, archiving artifacts, or cleaning up after the build, can be defined using the post { }
block in Declarative pipelines.
For example, converting a Scripted pipeline’s finally
block into a post
section in Declarative:
Scripted Pipeline:
node {
try {
stage('Build') {
echo 'Building...'
}
} finally {
echo 'Cleaning up...'
}
}
Declarative Pipeline:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building...'
}
}
}
post {
always {
echo 'Cleaning up...'
}
}
}
2.6 Implement Environment Variables
If you’re using environment variables in your Scripted pipeline, these can be translated into a Declarative pipeline using the environment { }
block.
Scripted Pipeline (Environment Variables):
node {
def myEnvVar = 'Hello'
stage('Print') {
echo "${myEnvVar}"
}
}
Declarative Pipeline (Environment Variables):
pipeline {
agent any
environment {
myEnvVar = 'Hello'
}
stages {
stage('Print') {
steps {
echo "${env.myEnvVar}"
}
}
}
}
2.7 Manage Conditions (if/else)
Conditional logic such as if/else
is more limited in Declarative pipelines but can be handled using when { }
blocks. For advanced logic, consider using script { }
to wrap Groovy code within a Declarative pipeline.
Here’s an example of translating conditional logic:
Scripted Pipeline:
node {
stage('Check') {
if (env.BRANCH_NAME == 'master') {
echo 'On master branch'
} else {
echo 'Not on master branch'
}
}
}
Declarative Pipeline:
pipeline {
agent any
stages {
stage('Check') {
steps {
script {
if (env.BRANCH_NAME == 'master') {
echo 'On master branch'
} else {
echo 'Not on master branch'
}
}
}
}
}
}
3. Example: Scripted Pipeline to Declarative Pipeline Conversion
Now, let’s go through a complete example of converting a Scripted pipeline to a Declarative pipeline.
Original Scripted Pipeline
node {
def buildResult = null
stage('Checkout') {
checkout scm
}
stage('Build') {
try {
echo 'Building...'
buildResult = sh(script: 'make build', returnStatus: true)
} catch (Exception e) {
buildResult = 1
}
}
stage('Test') {
if (
buildResult == 0) {
echo 'Testing...'
sh 'make test'
} else {
echo 'Skipping tests due to build failure'
}
}
stage('Deploy') {
if (buildResult == 0) {
echo 'Deploying...'
sh 'make deploy'
}
}
stage('Notify') {
if (buildResult == 0) {
echo 'Build succeeded!'
} else {
echo 'Build failed!'
}
}
}
Converted Declarative Pipeline
pipeline {
agent any
environment {
buildResult = 0
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
try {
echo 'Building...'
buildResult = sh(script: 'make build', returnStatus: true)
} catch (Exception e) {
buildResult = 1
}
}
}
}
stage('Test') {
when {
expression { buildResult == 0 }
}
steps {
echo 'Testing...'
sh 'make test'
}
}
stage('Deploy') {
when {
expression { buildResult == 0 }
}
steps {
echo 'Deploying...'
sh 'make deploy'
}
}
}
post {
always {
script {
if (buildResult == 0) {
echo 'Build succeeded!'
} else {
echo 'Build failed!'
}
}
}
}
}
4. Best Practices for Pipeline Conversion
- Start Simple: Begin by converting simple pipelines with basic stages, and then gradually add more complexity.
- Use Declarative Features: Take advantage of the features Declarative pipelines offer, like the
post { }
block for cleanup,when { }
conditions, andenvironment { }
variables. - Leverage Groovy: For complex logic that is difficult to implement directly in Declarative syntax, use the
script { }
block to insert Groovy code within a Declarative pipeline. - Test Iteratively: After converting, test each stage to ensure it runs as expected.
Conclusion
Converting an existing Scripted pipeline to a Declarative pipeline is a valuable step toward simplifying and standardizing your CI/CD processes. While Declarative pipelines might impose some restrictions, they improve pipeline readability, enforce best practices, and offer better error handling. By following a structured approach and understanding the key differences between the two pipeline types, you can smoothly transition your existing pipelines and benefit from the advantages of Declarative syntax.