Jenkins Declarative vs Scripted plugin

But let’s start with the following question - why are there two pipeline types in the first place? The scripted pipeline was the first implementation of the pipeline as a code in Jenkins. Even though it uses the underlying pipeline subsystem, it was designed more or less as a general-purpose DSL built with Groovy.[1] It means that it does not come with a fixed structure, and it is up to you how you will define your pipeline logic. The declarative pipeline, on the other hand, is more opinionated, and its structure is well-defined.[2] It may look a bit limiting, but in practice, you can achieve the same things using the scripted or declarative pipeline. So which one to choose?

If you ask me this question and expect an answer different from "it depends," I would say use the declarative pipeline. And here’s why

Pipeline code validation at startup

Let’s consider the following pipeline code

pipeline {

    agent any

    stages {

        stage("Build") {

            steps {

                echo "Some code compilation here..."

            }

        }

        stage("Test") {

            steps {

                echo "Some tests execution here..."

                echo 1

            }

        }

    }

}

If we try to execute the following pipeline, the validation will quickly fail the build. The echo step can be triggered only with the String parameter, so we get the error.

Notice that the pipeline didn’t execute any stage and just failed. This might save us a lot of time - imagine executing the Build stage for a couple of minutes, just to get the information that the echo step expects to get java.lang.String instead of java.lang.Integer

Now let’s take a look at the scripted pipeline equivalent of that example

node {

    stage("Build") {

        echo "Some code compilation here..."

    }

    stage("Test") {

        echo "Some tests execution here..."

        echo 1

    }

}

This pipeline executes the same stages and the same steps. There is one significant difference however

It failed as expected. But this time the Build stage was executed, as well as the first step from the Test stage. As you can see, there was no validation of the pipeline code. The declarative pipeline, in this case, handles such use cases much better

Restart from stage

Another cool feature that only declarative pipeline has is the "Restart from stage" one. Let’s fix the pipeline from the previous example and see if we can restart the Test stage only

pipeline {

    agent any

    stages {

        stage("Build") {

            steps {

                echo "Some code compilation here..."

            }

        }

        stage("Test") {

            steps {

                echo "Some tests execution here..."

            }

        }

    }

}

Let’s execute it

Here you can see that the Test stage is selected. There is an option called "Restart Test" right above the steps list on the right side. Let’s click on it and see the result.

As you can see, Jenkins skipped the Build stage (it used the workspace from the previous build) and started the next pipeline execution from the Test stage. It might be useful when you execute some external tests and they fail because of some issues with the remote environment. You can fix the problem with the test environment and re-run the stage again, without the need to rebuild all artifacts. (The code of the app hasn’t changed in such a case.)

Now, let’s look at the scripted pipeline example.

node {

    stage("Build") {

        echo "Some code compilation here..."

    }

    stage("Test") {

        echo "Some tests execution here..."

    }

}

No restart option as we can see.

Declarative pipeline options block

The third feature is supported by both pipeline types, however, the declarative pipeline handles it a bit better in my opinion. Let’s say we have the following features to add to the previous pipeline.

The timestamps in the console log.

The ANSI color output.

The 1-minute timeout for the Build stage, and 2 minutes timeout for the Test stage.

Here is what does the declarative pipeline looks like.

pipeline {

    agent any

    options {

        timestamps()

        ansiColor("xterm")

    }

    stages {

        stage("Build") {

            options {

                timeout(time: 1, unit: "MINUTES")

            }

            steps {

                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'

            }

        }

        stage("Test") {

            options {

                timeout(time: 2, unit: "MINUTES")

            }

            steps {

                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'

            }

        }

    }

}

In the declarative pipeline, options are separated from the pipeline script logic. The scripted pipeline also supports timestamps, ansiColor and timeout options, but it requires a different code. Here is the same pipeline expressed using the scripted pipeline.

node {

    timestamps {

        ansiColor("xterm") {

            stage("Build") {

                timeout(time: 1, unit: "MINUTES") {

                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'

                }

            }

            stage("Test") {

                timeout(time: 2, unit: "MINUTES") {

                    sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'

                }

            }

}

    }

}

I guess you see the problem. Here we used only timestamps and ansiColor Jenkins plugins. Imagine adding one or two more plugins

Skipping stages with when the block

The last thing I would like to mention in this blog post is the when a block that the declarative pipeline supports. Let’s improve the previous example and add the following condition:

Execute Test stage only if env.FOO equals bar.

Here is what the declarative pipeline code looks like

pipeline {

    agent any

    options {

        timestamps()

        ansiColor("xterm")

    }

    stages {

        stage("Build") {

            options {

                timeout(time: 1, unit: "MINUTES")

            }

            steps {

                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'

            }

        }

        stage("Test") {

            when {

                environment name: "FOO", value: "bar"

            }

            options {

                timeout(time: 2, unit: "MINUTES")

            }

            steps {

                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'

            }

        }

    }

}

The Test stage was skipped as expected. Now let’s try to do the same thing in the scripted pipeline example.

node {

    timestamps {

        ansiColor("xterm") {

            stage("Build") {

                timeout(time: 1, unit: "MINUTES") {

                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'

                }

            }

if (env.FOO == "bar") {

                stage("Test") {

                    timeout(time: 2, unit: "MINUTES") {

                        sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'

                    }

                }

            }

        }

    }

}

As you can see, we had to use if-condition to check if env.FOO equals bar, and only then add the Test stage. (It’s not a real skipping in this case, unfortunately.) Let’s run it and see what is the result

This is not the same result. In the scripted pipeline use case, the Test stage is not even rendered. This might introduce some unnecessary confusion, the declarative pipeline handles it much better in my opinion

Here are my top 4 differences between the declarative and scripted Jenkins pipeline. These are not the only differences, and I guess your list may look a little different

Recent Comments

No comments

Leave a Comment