jenkinsgroovyjenkins-pipeline

Extracting an entire Jenkins stage to a shared library?


Is it possible to take an entire stage('foo') {...} definition and extract it into a shared library within Jenkins? The docs are very clear on how to pull an individual step out, but I can't find any way to take an entire stage, parameterize it, and re-use it globally. I thought perhaps just return stage... would work, but it errors out as an invalid return value.


Solution

  • It depends if you use scripted or declarative pipeline.

    Scripted pipeline is more flexible and it allows you e.g. create stages based on some conditions (each pipeline run can have a different number and kind of stages). In this kind of pipeline you can extract a full stage to the shared library class and call it from inside the node {} block. Consider following example:

    // src/ScriptedFooStage.groovy
    class ScriptedFooStage {
        private final Script script
    
        ScriptedFooStage(Script script) {
            this.script = script
        }
    
        // You can pass as many parameters as needed
        void execute(String name, boolean param1) {
            script.stage(name) {
                script.echo "Triggering ${name} stage..."
                script.sh "echo 'Execute your desired bash command here'"
    
                if (param1) {
                    script.sh "echo 'Executing conditional command, because param1 == true'"
                }
            }
        }
    }
    

    Then the Jenkinsfile may look like this:

    node {
        new ScriptedFooStage(this).execute('Foo', true)
    }
    

    As you can see the whole stage was encapsulated in the ScriptedFooStage.execute() method. Its name is also taken from the parameter name - scripted pipeline allows you doing such thing.


    Declarative pipeline on the other hand is more strict and opinionated. It's fixed if it comes to the number of stages and their names (you can't model dynamically what stages are present per build and what are their names). You can still take advantage of shared library classes, but you are limited to execute them inside script {} block inside stage('Name') { steps {} } block. It means that you can't extract the whole stage to the separate class, but only some part that gets executed at the steps level. Consider following example:

    // src/DeclarativeFooStage.groovy
    class DeclarativeFooStage {
        private final Script script
    
        DeclarativeFooStage(Script script) {
            this.script = script
        }
    
        // You can pass as many parameters as needed
        void execute(String name, boolean param1) {
            script.echo "Triggering script with name == ${name}"
            script.sh "echo 'Execute your desired bash command here'"
    
            if (param1) {
                script.sh "echo 'Executing conditional command, because param1 == true'"
            }
        }
    }
    

    And the Jenkinsfile may look like this:

    // Jenkinsfile
    pipeline {
        agent any
    
        stages {
            stage('Foo') {
                steps {
                    script {
                        new DeclarativeFooStage(this).execute('something', false)
                    }
                }
            }
        }
    }
    

    If we would try execute new DeclarativeFooStage(this).execute('something', false) outside script {} block in the declarative pipeline we would get compilation errors.

    Conclusion

    The choice between scripted or declarative pipeline depends on specific use case. If you want to get the best flexibility when it comes to modeling your pipeline business logic, scripted pipeline might be the good choice. However, it comes with some price. For instance, scripted pipeline does not support restarting pipeline build from specific stage - this is supported only by declarative pipeline. (Imagine you have 10 stages in the pipeline and stage 7 failed because of some silly mistake and you would like to restart the build from 7th stage - in scripted pipeline you would have to re-run from the very beginning, while declarative pipeline can restart from 7th stage by remembering the results from all 6 previous stages).