Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a Jenkins pipeline have an optional input step?

Is it possible to create a Jenkins pipeline with an optional input stage?

The below snippet doesn't achieve this goal.

Expected behaviour

The stage (and therefore the input prompt) should only run for specific branches.

Actual behaviour

This stage runs for all branches. The when filter is ignored when an input step is used.

stage('Approve') {
            when {
                expression { BRANCH_NAME ==~ /^qa[\w-_]*$/ }
            }
            input {
                message "Approve release?"
                ok "y"
                submitter "admin"
                parameters {
                    string(name: 'IS_APPROVED', defaultValue: 'y', description: 'Deploy to master?')
                }
            }
            steps {
                script {
                    if (IS_APPROVED != 'y') {
                        currentBuild.result = "ABORTED"
                        error "User cancelled"
                    }
                }
            }
        }
like image 879
Learner Avatar asked Feb 16 '18 07:02

Learner


People also ask

Can a Jenkins stage have multiple steps?

Jenkins Pipeline allows you to compose multiple steps in an easy way that can help you model any sort of automation process. Think of a "step" like a single command which performs a single action. When a step succeeds it moves onto the next step. When a step fails to execute correctly the Pipeline will fail.

How do you skip a step in Jenkins pipeline?

You can skip stages in declarative pipelines using when , so the following should work. stages { stage('Deploy') { when { equals expected: true, actual: Deploy } steps { // ... } } } If it should be totally invisible in the review pipeline, then use scripted pipelines and wrap the stage with an if statement.


1 Answers

The filter is not ignored, it is just evaluated after the input step. In your example, you would always be asked whether to deploy, and in the case that you are not on a QA branch, nothing would happen.

Now you could ask why Jenkins isn't evaluating the 'when' directive first. In that case, you could not use the input parameter in your when condition.

And having multiple when directives would be like scripting within the declarative pipeline.

However, there is an expression that allows you controlling when the 'when' directive is evaluated. This is beforeAgent. It allows you to evaluate the when statement before the agent is allocated. Similar to that, you would need something like beforeInput. You could create a feature request for that.

I stepped away from using the input directive and I use input within a scripting block now, because that provides much more flexibility, e.g. I am sending Slack notifications when somebody has to approve something, which is impossible with the declarative approach. You would need a notify directive for that. And if there was one, is that going to be evaluated before or after the input step?

You see, doing everything declarative is not always the best way. So my recommended approach is the following (disclaimer: this is untested!):

pipeline {
  // We want to use agents per stage to avoid blocking our build agents
  // while we are waiting for user input.
  agent none
  ...
  // The question mark naming convention is helpful to show you which
  //  approval stage belongs to which work stage.
  stage('Release?') {
    // Don't allocate an agent because we don't want to block our
    // slaves while waiting for user input.
    agent none
    when {
      // You forgot the 'env.' in your example above ;)
      expression { env.BRANCH_NAME ==~ /^qa[\w-_]*$/ }
    }
    options {
      // Optionally, let's add a timeout that we don't allow ancient
      // builds to be released.
      timeout time: 14, unit: 'DAYS' 
    }
    steps {
      // Optionally, send some notifications to the approver before
      // asking for input. You can't do that with the input directive
      // without using an extra stage.
      slackSend ...

      // The input statement has to go to a script block because we
      // want to assign the result to an environment variable. As we 
      // want to stay as declarative as possible, we put noting but
      // this into the script block.
      script {
        // Assign the 'DO_RELEASE' environment variable that is going
        //  to be used in the next stage.
        env.DO_RELEASE = input ...
      }
      // In case you approved multiple pipeline runs in parallel, this
      // milestone would kill the older runs and prevent deploying
      // older releases over newer ones.
      milestone 1
    }
  }
  stage('Release') {
    // We need a real agent, because we want to do some real work.
    agent any
    when {
      // Evaluate the 'when' directive before allocating the agent.
      beforeAgent true
      // Only execute the step when the release has been approved.
      environment name: 'DO_RELEASE', value: 'yes'
    }
    steps {
      // Make sure that only one release can happen at a time.
      lock('release') {
        // As using the first milestone only would introduce a race 
        // condition (assume that the older build would enter the 
        // milestone first, but the lock second) and Jenkins does
        // not support inter-stage locks yet, we need a second 
        // milestone to make sure that older builds don't overwrite
        // newer ones.
        milestone 2

        // Now do the actual work here.
        ...
      }
    }
  }
like image 128
Hendrik M Halkow Avatar answered Nov 12 '22 08:11

Hendrik M Halkow