Jenkins

Jenkins

Environment variables

video blog

GitHub repo

  • In echo variables can be accessed using either ${env.variable_name} or ${variable_name}

  • In sh variables can only be accessed using ${variable_name}

  • stage-level environment block variables can override pipeline-level (global) environment block variables

  • But env variables inside script block cannot override global-env or stage-env variables

  • variables inside withEnv block can override all levels of env vars.

  • If we assign a true or false value to a variable. It will be treated as a string. You need to typecast explicitly using toBoolean()

  • Generally sh won`t return any output, so if you want to get any output out of sh use it with a script like this
    COUNT_FILES= sh(script: "ls -la /tmp | tail -n +4 | wc -l", returnStdout: true)

  • Take care when using a null value, because if a function returns null and you capture it in a variable. It is most likely to store a string with the value null. Use Elvis operator as shown below

  • If you want to access variable inside a script of particular stage outside its scope(i.e. any where in the pipeline including post block then create a variable using env and assign it to that)
    EG: In Jenkins pipelines, variables defined within a script block using def are scoped to that block. This means that you won't be able to access them outside of that script block directly. However, if you want to access a variable in the post block, you can set it as an environment variable or use other mechanisms to make it globally accessible.

    Here are a couple of methods to achieve this:

    1. Using env to Set Environment Variables:

      Inside your script block, you can set the variable as an environment variable using the env global variable. This way, the value becomes accessible throughout the Jenkins pipeline, including in the post block.

       groovyCopy codestage('Some Stage') {
           steps {
               script {
                   def myLocalVar = "someValue"
                   env.MY_GLOBAL_VAR = myLocalVar
               }
           }
       }
      
       post {
           always {
               script {
                   echo "Value of MY_GLOBAL_VAR: ${env.MY_GLOBAL_VAR}"
               }
           }
       }
      
      • In Jenkins, parameters declared for a pipeline are automatically made available as environment variables. This means if you have a parameter named MY_PARAM, you can access its value anywhere in the pipeline script using env.MY_PARAM.

        Here's an example of how you can use pipeline parameters:

          groovyCopy codepipeline {
              agent any
        
              parameters {
                  string(name: 'MY_PARAM', defaultValue: 'Default Value', description: 'A description of my parameter')
              }
        
              stages {
                  stage('Print Parameter') {
                      steps {
                          echo "Parameter value is: ${env.MY_PARAM}"
                      }
                  }
        
                  stage('Another Stage') {
                      steps {
                          script {
                              if (env.MY_PARAM == 'SomeValue') {
                                  echo "Parameter is set to SomeValue"
                              } else {
                                  echo "Parameter is set to something else"
                              }
                          }
                      }
                  }
              }
        
              post {
                  always {
                      echo "Post block: Parameter value is: ${env.MY_PARAM}"
                  }
              }
          }
        

        Very very Imp:

        In a Jenkins pipeline, you can access environment variables inside a script block as well as inside an sh step. Environment variables can be accessed using the env prefix.

        Here are some examples to illustrate this:

        1. Accessing env variable inside a script block:

          groovyCopy codepipeline {
              agent any
              environment {
                  MY_ENV_VAR = 'Hello from env'
              }
              stages {
                  stage('Demo Stage') {
                      steps {
                          script {
                              echo "Accessing env variable in script block: ${env.MY_ENV_VAR}"
                          }
                      }
                  }
              }
          }
        

        2. Accessing env variable inside an sh step within a script block:

          groovyCopy codepipeline {
              agent any
              environment {
                  MY_ENV_VAR = 'Hello from env'
              }
              stages {
                  stage('Demo Stage') {
                      steps {
                          script {
                              echo "Accessing env variable in script block: ${env.MY_ENV_VAR}"
        
                              // Accessing inside sh step
                              sh """
                              echo "Accessing env variable in sh block: $MY_ENV_VAR"
                              """
                          }
                      }
                  }
              }
          }
        

        Note that when you access the environment variable inside the sh step, you don't need to use the env prefix. You can simply reference the variable by its name.

        However, remember that this works because Groovy is interpolating the variable inside the double-quoted string ("""..."""). If you used single quotes ('''...'''), the variable would not be interpolated.

        The code snippet used in the video

    pipeline{

        agent { label 'slave' }
        environment{
            // Available to all stages
            NAME = "Bhuvan"
            ID = 90
        }
        stages{
            stage("Print default vars"){
                steps{
                    sh 'printenv | sort'
                }
            }

             stage("Use Env vars"){
                environment{
                    // Available to this particular stage only
                    NAME ="Chand"
                    ID = 30
                }
                steps{
                    // Here env is optional we can use ${BUILD_NUMBER} directly but it is best practice to use env.
                    echo "Build Number = ${env.BUILD_NUMBER}"
                    echo "Build Number(without env.) = ${BUILD_NUMBER}"

                    // It overrides global username
                    echo "NAME = ${env.NAME}" // chand
                    // For shell we don`t use env. If we use we ill get error
                    sh 'echo ${NAME}' // chand

                    // Even number will be treated as string, as you can see here
                    echo "ID = ${env.ID} and its type is ${env.ID.class}" //ID = 30 and its type is class java.lang.String

                    script{
                        env.GROUP_NAME = "test-group"
                        env.TRIGGER_NEXT=true
                        env.NAME = "dummy-name"
                    }
                    echo "Group name =${env.GROUP_NAME}" // test-group
                    sh 'echo group name is ${GROUP_NAME}' // test-group
                    // script block cannot overide name
                    echo "NAME is: ${env.NAME}" // chand

                    withEnv(["USER_PWD=secret","USER_IS_ADMIN=false","NAME=Maddi"]){
                    echo "user password is: ${env.USER_PWD}" // secret
                    sh 'echo user is admin?: ${USER_IS_ADMIN}' // false
                    echo "Name is: ${env.NAME}" // Maddi
                    }
                }
            }

            stage("Using boolean env vars"){
                when{
                    expression{
                       // env.TRIGGER_NEXT == true --> This will result in error because env.TRIGGER_NEXT will be treated as a string
                        env.TRIGGER_NEXT.toBoolean() == true
                    }
                }
                // other way is to use when {environmanet name: "TRIGGER_NEXT", value = "true" and set value in script block to string by double quoating "true"}
                steps {
                    echo "Type of trigger next is: ${env.TRIGGER_NEXT.class}"
                    echo "Type of trigger next is: ${env.TRIGGER_NEXT.toBoolean().class}"
                }

            }
            stage("capturing output from other step"){
                environment{
                    // By default sh doesn`t return anything so sh 'ls -la /tmp | tail -n +4 | wc -l' output will be null.
                    COUNT_FILES= sh(script: "ls -la /tmp | tail -n +4 | wc -l", returnStdout: true)
                }
                steps{
                    echo "No of files in /tmp folder are: ${env.COUNT_FILES}"
                }
            }
            stage("Handling null values"){
                environment{
                    // SOME_VAL = someVal() //It captures string(null value string with 4 characters) not null 
                    // This is elvis opetaror. Refere blog: https://blog.mrhaki.com/2009/08/groovy-goodness-elvis-operator.html  
                    SOME_VAL = "${someVal() ?: ''}"
                }
                steps{
                    echo "The value of someval is ${env.SOME_VAL} and its type is ${env.SOME_VAL?.class}"
                }
            }

        }
    }
    def someVal(){
        return null
    }

Script and steps blocks:

Use steps block inside the stage wherever possible. If something cannot be achieved using the steps block then use the script block. You can even use scripts bloc within steps.

  1. Using Only steps Block: This is the most common and recommended way to define steps in a Declarative pipeline. You can define individual steps directly within the steps block without using a separate script block.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     echo "Hello from the steps block!"
                     sh 'ls -la'
                 }
             }
         }
     }
    
  2. Using Only script Block: If you need to execute complex scripted Groovy code or use scripted pipeline steps that are not available as Declarative steps, you can use only a script block. In this case, the steps block is not required.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 script {
                     echo "Hello from the script block!"
                     sh 'ls -la'
                 }
             }
         }
     }
    
  3. Using script Inside steps Block: You can use a script block within a steps block when you need to perform complex or custom logic for a specific step.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     script {
                         echo "Running complex logic here..."
                         def result = sh(returnStdout: true, script: 'echo "Hello from the script!"').trim()
                         echo "Script Result: ${result}"
                     }
                     echo "Continuing with regular steps..."
                 }
             }
         }
     }
    
  4. Using Multiple script Blocks Inside steps Block: You can use multiple script blocks within a steps block when you have separate steps that require custom scripted logic.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     script {
                         echo "Step 1: Performing a custom action..."
                     }
                     script {
                         echo "Step 2: Running a custom script..."
                         sh 'ls -la'
                     }
                 }
             }
         }
     }
    

Remember that while the script block allows you to perform advanced and custom logic, it's best to use Declarative pipeline steps whenever possible. Declarative steps provide better readability and maintainability, and using them follows the spirit of the Declarative pipeline approach. Reserve the script block for situations where you genuinely need scripted Groovy code or require specific scripted pipeline steps that are not available in Declarative steps.

Capturing the output or return code of a command

Capturing the exit status: If you want to capture the exit status of the shell command (0 for success, non-zero for failure), you can use the returnStatus option. When returnStatus is true, the sh step will return the exit status of the command instead of its output. Here's an example:

    pipeline {
        agent any
        stages {
            stage('Example') {
                steps {
                    script {
                        def exitStatus = sh(returnStatus: true, script: 'ls some_nonexistent_directory')
                        if (exitStatus == 0) {
                            echo "Command succeeded!"
                        } else {
                            echo "Command failed with exit code: ${exitStatus}"
                        }
                    }
                }
            }
        }
    }

Capturing the command output: If you want to capture the output (standard output) of the shell command, you can assign the sh step to a variable. The sh step will return the output as a string. Here's an example:

    groovyCopy codepipeline {
        agent any
        stages {
            stage('Example') {
                steps {
                    script {
                        def commandOutput = sh(script: 'echo "Hello, Jenkins!"', returnStdout: true).trim()
                        echo "Command output: ${commandOutput}"
                    }
                }
            }
        }
    }

Script Block

In Jenkins, the script block is a section in a Jenkins pipeline where you can write and execute arbitrary Groovy code. The script block is used when you need to perform more complex logic or need access to the underlying Jenkins API that is not available directly in declarative pipeline steps.

  1. Within a steps block: Although most steps in Declarative pipelines are designed to work without needing a script block, there may be situations where you want to include one within the steps block. This can be useful if you need to perform more complex logic. Here's an example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     script {
                         // Your scripted Groovy code here
                         def message = "Hello, Jenkins!"
                         echo message
                     }
                 }
             }
         }
     }
    
  2. Within a post block: The post block allows you to define actions that should be executed after the entire pipeline or specific stages have completed. If you need to include scripted Groovy logic in a post-action, you can use a script block within the post block. Here's an example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     // Your regular steps here
                 }
             }
         }
         post {
             always {
                 script {
                     // Your scripted Groovy code here
                     echo "This will always run, regardless of the pipeline result."
                 }
             }
             success {
                 script {
                     // Your success-specific scripted Groovy code here
                     echo "This will run only if the pipeline is successful."
                 }
             }
             failure {
                 script {
                     // Your failure-specific scripted Groovy code here
                     echo "This will run only if the pipeline fails."
                 }
             }
         }
     }
    

Please note that while the script block can be powerful, it's best to use Declarative pipeline steps whenever possible, as they provide a higher level of abstraction and better readability. Only use the script block when you need to perform advanced or custom logic that cannot be achieved using regular Declarative pipeline steps.

Functions & Conditional statements

Functions blog: here
Conditional statements(if/else and when) blog: here

Note: If you want to capture the return type of any function. There are 2 ways.

  1. If you are using steps{} block(declarative syntax with simple logic). use def in front of a variable name as shown.

     def myFunction() {
         return "Hello from the function!"
     }
    
     pipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     // Calling the function directly in the steps block
                     def result = myFunction()
                     echo "Function Return Value: ${result}"
                 }
             }
         }
     }
    
  2. If you are using script block then no worries. You can directlyc capture it

     def mul()
     {
        def a=2
        def b=3
        def mul=a*b
        return mul
     }
    
     pipeline {
         agent any
         stages {
             stage('Hello') {
                 steps{
                     script{
                       output= mul()
                       echo "The mul is ${output}"
                 }
                 }
             }
         }
         }
    

Scope of variables in Jenkins Declarative pipeline

  1. Variables in a Stage: Variables defined within a stage using the environment directive are only available within that stage. They are not accessible in other stages or post blocks. This is because each stage in a Declarative pipeline runs in a separate workspace and has its own environment.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Stage 1') {
                 environment {
                     MY_VARIABLE = "Value 1"
                 }
                 steps {
                     echo "Variable in Stage 1: ${MY_VARIABLE}"
                 }
             }
             stage('Stage 2') {
                 steps {
                     echo "Variable in Stage 2: ${MY_VARIABLE}"  // This will not work
                 }
             }
         }
     }
    
  2. Variables in a Script Block: Variables defined(using def) inside a script block within a stage or post block have local scope and are limited to that specific block. They are not accessible outside of the block.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Example') {
                 steps {
                     script {
                         def localVariable = "Local Value"
                         echo "Local Variable: ${localVariable}"
                     }
                 }
             }
         }
         post {
             always {
                 script {
                     echo "Post Block Variable: ${localVariable}"  // This will not work
                 }
             }
         }
     }
    

    Note: However if you want to use a variable defined in stage 1 in stage 2, then you need to use env variable in stage 1 instead of local def.
    Example:
    Here's an example using environment variables to share data between stages:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Stage 1') {
                 steps {
                     script {
                         // Define a variable in Stage 1
                         env.MY_VARIABLE = 'Data from Stage 1'
                     }
                 }
             }
             stage('Stage 2') {
                 steps {
                     script {
                         // Access the variable defined in Stage 1
                         def dataFromStage1 = env.MY_VARIABLE
                         echo "Data from Stage 1: ${dataFromStage1}"
                     }
                 }
             }
         }
     }
    

    In this example, MY_VARIABLE is defined in "Stage 1" and then accessed in "Stage 2" using the env object. This allows you to pass data between stages through environment variables.

  3. Variables in Nested Stages and Post Blocks: Variables defined within nested stages or post blocks have the same scope rules as described above. Nested stages or post blocks do not have access to variables defined in their parent stages or vice versa.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             stage('Parent Stage') {
                 steps {
                     script {
                         def parentVariable = "Parent Value"
                         echo "Parent Variable: ${parentVariable}"
                     }
                 }
                 stage('Nested Stage') {
                     steps {
                         script {
                             def nestedVariable = "Nested Value"
                             echo "Nested Variable: ${nestedVariable}"
                             echo "Parent Variable in Nested Stage: ${parentVariable}"  // This will not work
                         }
                     }
                 }
             }
         }
     }
    
  4. you can access variables defined in the parent stage using the environment block within a nested stage. The environment block defined at the parent stage level will be available to all child stages within that stage's workspace.

Here's the updated example to demonstrate this:

    groovyCopy codepipeline {
        agent any
        stages {
            stage('Parent Stage') {
                environment {
                    PARENT_VARIABLE = "Parent Value"
                }
                steps {
                    echo "Parent Variable: ${PARENT_VARIABLE}"
                }
                stage('Nested Stage') {
                    steps {
                        script {
                            echo "Nested Variable: ${PARENT_VARIABLE}"  // This will work
                        }
                    }
                }
            }
        }
    }

In conclusion, variables defined in one stage or context (e.g., script block) are not directly accessible in other stages, nested stages, or post blocks. If you need to share data between different stages, you can use external means like writing to files, using Jenkins workspace, or using shared libraries. Alternatively, you can also pass parameters between stages using the input step or using the stash and unstash steps to move files between stages.

Note: If we declare a variable using def then only that variable can be accessed within script block. But if we define a variable without def just like other variable then it can be accessed outside of the script block as well. See in below example

    pipeline {
        agent any
        stages {
            stage('Example') {
                steps {
                    script {
                        // Local variable declaration within the script block
                        def localVariable = "Local Value"
                        NORMAL_VAR = "Normal var"
                        echo "Local Variable: ${localVariable}" // Local Value
                        // Since NORMAL_VAR is in script block don1t use env. before variable name which resolves to null
                        echo "Normal variable: ${NORMAL_VAR}" // Normal var
                        echo "Normal variable with env: ${env.NORMAL_VAR}" //null
                    }
                    // Since NORMAL_VAR is in script block don1t use env. before variable name which resolves to null
                    echo "Normal Variable outside script block: ${NORMAL_VAR}" // Normal var

                }
            }
        }
    }

Post block

In Jenkins Declarative pipeline, the post block is used to define actions that should be executed after the main stages of the pipeline have been completed. It allows you to specify different behaviours based on the outcome of the pipeline or specific stages within the pipeline. The post block contains one or more postconditions, which can be associated with a specific result (e.g., success, failure) or run unconditionally.

The syntax of the post block is as follows:

    groovyCopy codepipeline {
        // Pipeline configuration

        post {
            // Post conditions go here
        }
    }

Within the post block, you can define different sections, each associated with a specific outcome of the pipeline run. The available sections are:

  1. always: The always section specifies actions that should be executed regardless of the pipeline result. This section is always executed, regardless of whether the pipeline is successful or not.

     groovyCopy codepost {
         always {
             // Actions to run always
         }
     }
    
  2. success: The success section specifies actions that should be executed only if the pipeline is successful (i.e., all stages are completed without any failure).

     groovyCopy codepost {
         success {
             // Actions to run on successful pipeline
         }
     }
    
  3. failure: The failure section specifies actions that should be executed only if the pipeline fails (i.e., at least one stage fails).

     groovyCopy codepost {
         failure {
             // Actions to run on failed pipeline
         }
     }
    
  4. unstable: The unstable section specifies actions that should be executed if the pipeline result is unstable (e.g., some tests failed, but the build itself succeeded).

     groovyCopy codepost {
         unstable {
             // Actions to run on unstable pipeline
         }
     }
    
  5. changed: The changed section specifies actions that should be executed only if the state of the pipeline has changed from the previous run. This can be useful when triggering actions based on code changes or other external factors.

     groovyCopy codepost {
         changed {
             // Actions to run if the pipeline state has changed
         }
     }
    
  6. Combining Post Conditions: You can combine multiple post conditions within the post block to specify different actions based on different pipeline outcomes.

     groovyCopy codepost {
         always {
             // Actions to run always
         }
         success {
             // Actions to run on successful pipeline
         }
         failure {
             // Actions to run on failed pipeline
         }
     }
    

Remember that each section of the post block is optional, and you can include only the sections that are relevant to your pipeline. The sections are executed in the order they are defined.

The post block is a powerful feature in Declarative pipelines that allows you to handle post-build actions efficiently and cleanly. It can be used to perform tasks such as sending notifications, cleaning up resources, publishing reports, and more based on the pipeline's results.

Commonly used pre-defined function

In Jenkins Declarative pipeline, there are indeed some commonly used predefined functions and steps that are specifically designed to be used within the post block. Here are a few of them:

  1. cleanWs: The cleanWs function is used to clean up the workspace at the end of a pipeline run. It helps to free up disk space by deleting the workspace of the current build.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             // Define stages
         }
         post {
             always {
                 cleanWs()
             }
         }
     }
    
  2. archiveArtifacts: The archiveArtifacts step is used to archive artifacts (e.g., build outputs) after a successful build. The artifacts can be accessed later or used for deployments.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             // Define stages
         }
         post {
             success {
                 archiveArtifacts artifacts: '**/*.jar', allowEmptyArchive: true
             }
         }
     }
    
  3. mail: The mail step is used to send an email notification after the pipeline run. It is useful for notifying team members or stakeholders about the build result.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             // Define stages
         }
         post {
             failure {
                 mail to: 'team@example.com', subject: 'Pipeline Failed', body: 'The pipeline has failed.'
             }
         }
     }
    
  4. slackSend: The slackSend step is used to send messages to Slack channels. It is helpful for integrating Jenkins with Slack for build notifications.

    Example:

     groovyCopy codepipeline {
         agent any
         stages {
             // Define stages
         }
         post {
             always {
                 slackSend message: 'Pipeline completed.', channel: '#build-notifications'
             }
         }
     }
    

These are just a few examples of commonly used predefined functions and steps in the post block of Jenkins Declarative pipeline. There are many more available, and you can find additional functions provided by plugins or write your own custom functions as needed. The post block is a powerful mechanism to execute specific actions based on the outcome of the pipeline, and it enables you to perform various post-build tasks like cleanup, notifications, archiving, etc.

Multi Branch pipeline

Article Video

Environment variables available throughout the pipeline:

  • Environment variables defined in the below 3 ways can be accessed anywhere in the pipeline.
    1. global environment block
    2. external files

    3. pipeline parameters

    Eg: Supply environment variables using external file using load
    In a Jenkins Declarative Pipeline, you can use the load step to read and evaluate Groovy scripts from external files. This can be useful for modularizing your pipeline code or for reading configuration or shared code from external files. Here's how you can use the load step:

    1. Create an external Groovy script file. This file can contain any Groovy code you want to use in your pipeline. For example, you can create a file named myScript.groovy with the following content:
    // App vars
    env.APP_NAME = 'dummy-app'
  1. In your Jenkins Declarative Pipeline script, use the load step to load and evaluate the external script file. You can then call functions or use variables defined in the loaded script:
    pipeline {
        agent any
        stages {
            stage('Load Script') {
                steps {
                    script {
                       load("myScript.groovy")   
                       echo "App name: ${env.APP_NAME}"
                    }
                }
            }
        }
    }

In this example, we load the myScript.groovy file using load, and then we call the env.APP_NAME variable defined in that external script.

Make sure the external script file is located in the same directory as your Jenkinsfile or provide the full path to the script file if it's located elsewhere. Additionally, ensure that the script file is secure and trusted, as loading external scripts can introduce security risks.

  • So when the same variable is declared in all 3 places. Then the precedence order will be like this.
    Global env >> external file >> pipeline parameters

  • Example: Extend the above example by providing the same APP_NAME env in other 2 locations i.e. global environment block and pipeline parameter.

  •       pipeline {
              agent any
              environemnt {
              APP_NAME = "dummy value from global env block"
              } 
              stages {
                  stage('Load Script') {
                      steps {
                          script {
                             load("myScript.groovy")   
                             echo "App name: ${env.APP_NAME}"
                          }
                      }
                  }
              }
          }
    

    Defined a string parameter to the pipeline with the same value

    case 1: If I trigger the pipeline now then I will get "dummy value from global env block" as ouput(since it has highest precedence)

    case 2: comment envi block var and the trigger pipeline again, then you will get "dummy app" from the external file as output

    case3: Now comment the external file value also and trigger pipeline finally you will get "dummy app from params"