Jenkins Declarative Pipeline detect first run and fail when choice parameters present - jenkins

I often write Declarative Pipeline jobs where I setup parameters such as "choice". The first time I run the job, it blindly executes using the first value in the list. I don't want that to happen. I want to detect this case and not continue the job if a user didn't select a real value.
I thought about using "SELECT_VALUE" as the first item in the list and then fail the job if that is the value. I know I can use a 'when' condition on each stage, but I'd rather not have to copy that expression to each stage in the pipeline. I'd like to fail the whole job with one check up front.
I don't like the UI for 'input' tasks because the controls are hidden until you hover over a running stage.
What is the best way to validate arguments with a Declarative Pipeline? Is there a better way to detect when the job is run for the first time and stop?

I've been trying to figure this out myself and it looks like the pipeline runs with a fully populated parameters list.
So, the answer to your choice option is to make the first item a value like "please select option" and have your code use when to check that
For example
def paramset = true
pipeline {
parameters {
choice(choices: ['select','test','proof', 'prod'], name: 'ENVI')
}
stages {
stage ('check') {
when { expression { return params.choice.ENVI == 'select' }
steps {
script {
echo "Missing parameters"
paramset = false
}
}
}
stage ('step 1') {
when { expression { return paramset }
steps {
script {
echo "Doing step 1"
}
}
}
stage ('step 2') {
when { expression { return paramset }
steps {
script {
echo "Doing step 2"
}
}
}
stage ('step 3') {
when { expression { return paramset }
steps {
script {
echo "Doing step 3"
}
}
}
}
}

Related

Can Jenkins pipelines have variable stages?

From my experience with Jenkins declarative-syntax pipelines, I'm aware that you can conditionally skip a stage with a when clause. E.g.:
run_one = true
run_two = false
run_three = true
pipeline {
agent any
stages {
stage('one') {
when {
expression { run_one }
}
steps {
echo 'one'
}
}
stage('two') {
when {
expression { run_two }
}
steps {
echo 'two'
}
}
stage('three') {
when {
expression { run_three }
}
steps {
echo 'three'
}
}
}
}
...in the above code block, there are three stages, one, two, and three, each of whose execution is conditional on a boolean variable.
I.e. the paradigm is that there is a fixed superset of known stages, of which individual stages may be conditionally skipped.
Does Jenkins pipeline script support a model where there is no fixed superset of known stages, and stages can be "looked up" for conditional execution?
To phrase it as pseudocode, is something along the lines of the following possible:
my_list = list populated _somehow_, maybe reading a file, maybe Jenkins build params, etc.
pipeline {
agent any
stages {
if (stage(my_list[0]) exists) {
run(stage(my_list[0]))
}
if (stage(my_list[1]) exists) {
run(stage(my_list[1]))
}
if (stage(my_list[2]) exists) {
run(stage(my_list[2]))
}
}
}
?
I think another way to think about what I'm asking is: is there a way to dynamically build a pipeline from some dynamic assembly of stages?
For dynamic stages you could write either a fully scripted pipeline or use a declarative pipeline with a scripted section (e. g. by using the script {…} step or calling your own function). For an overview see Declarative versus Scripted Pipeline syntax and Pipeline syntax overview.
Declarative pipeline is better supported by Blue Ocean so I personally would use that as a starting point. Disadvantage might be that you need to have a fixed root stage, but I usually name that "start" or "init" so it doesn't look too awkward.
In scripted sections you can call stage as a function, so it can be used completely dynamic.
pipeline {
agent any
stages {
stage('start') {
steps {
createDynamicStages()
}
}
}
}
void createDynamicStages() {
// Stage list could be read from a file or whatever
def stageList = ['foo', 'bar']
for( stageName in stageList ) {
stage( stageName ) {
echo "Hello from stage $stageName"
}
}
}
This shows in Blue Ocean like this:

Jenkins pipeline: detect if a stage is started with the "Restart from stage" icon

Let's say I have a declarative pipeline. I want to run a stage only when 'Restart from stage' icon is used ?
Is there a way to do this (a method, a variable...)? I want to run the stage only if "Restart from stage" is used
stage('Test') {
when {
expression {
// An expression to detect if Restart from this stage is used
}
}
steps {
sh 'echo 1'
}
}
You can define a global variable that will hold a Boolean value representing if the pipeline was executed from the beginning or from a specific stage, update it in your first stage and use it later on in the when condition to determine if a restart from stage has occurred.
Something like:
RESTART = true
pipeline {
agent any
stages {
stage('Setup') {
steps {
script{
// signaling pipeline was executed from the beginning (first stage)
RESTART = false
}
// other setup steps
}
}
stage('Test') {
when {
expression { return RESTART }
}
steps {
sh 'echo 1'
}
}
}
}
Another nice option based on #Pamela's answer for using a cause condition, is to use the built in triggeredBy option in the when directive, thus avoiding the need to use getBuildCauses() and the need to filter all causes, and instaed get the condition out of the box.
Something like:
stage('Test') {
when { triggeredBy 'RestartDeclarativePipelineCause' }
steps {
sh 'echo 1'
}
}
You can use currentBuild.getBuildCauses(): https://www.jenkins.io/doc/pipeline/examples/#get-build-cause
Then, in your Test stage add when expression checking the cause of the build matches the one you need.
stage('Test') {
when {
expression {
return currentBuild.getBuildCauses().any { cause ->
cause._class == 'org.jenkinsci.plugins.pipeline.modeldefinition.causes.RestartDeclarativePipelineCause'
}
}
}
steps {
sh 'echo 1'
}
}

Need Condition based Input Step in Jenkins pipeline script

I have three stages in jenkins pipeline script viz (1) precheck, (2) build-prod & (3) build-dr.
I have stated the input step for manual trigger at stage "build-dr"
My pipeline is condition based i.e based on user parameter in precheck stage .
Condition1: "precheck" -> "build-prod" and then "build-dr" is executed.
Condition2: "precheck" and then "build-dr" is executed (skips build-prod).
I need the input step in Condition1 and is working fine however the input step should not be executed for Condition2 i.e no popup msg with input step. Please let me know how can we put a condition around input step in stage 3 build-dr so it does not execute input step when the pipleline skips (2) build-prod.
Jenkins pipeline Script Code:
agent any
stages {
stage ("Pre-Check Parameters") {
steps {
echo "Pre-Check called in pipeline"
}
}
stage ("build-prod") {
when {
expression { params.region == 'prod_only' || params.region == 'prod_and_dr' }
}
steps {
build 'job4'
}
}
stage ("build-dr") {
input{
message "Proceed or Abort"
submitter "user1,admin"
parameters {
string(name:'username', defaultValue: 'user1', description: 'Username of the user pressing Ok')
}
}
when {
expression { params.region == 'dr_only' || params.region == 'prod_and_dr'}
}
steps {
build 'job5'
}
}
}
}
Kindly suggest.
You are currently using the input directive as described here but this prevents you to make this input conditional. You actually have to use the Input Step. Instead of adding the input field directly below the stage directive you move this into the steps block of your stage and add a script block around it to use if/else conditionals.
And take care to remove the curly brackets around you input step and to add a colon after each property.
What you have to do now is to adapt this line to your requirements:
if(Condition1 == true) Depenending on the value of your parameter.
stage("build-dr") {
when {
expression { params.region == 'dr_only' || params.region == 'prod_and_dr'}
}
steps {
script {
if(Condition1 == true) {
input message: "Proceed or Abort", submitter: "user1,admin",
parameters: [string(name:'username', defaultValue: 'user1', description: 'Username of the user pressing Ok')]
}
}
build 'job5'
}
}
Alternatively you can use an environment declaration block to declare a variable and assign a specific value to it if your second stage will be executed. But any environment value will always be typed as a String this is important for the if/else conditional. A good way to assign a value to the variable would be to add a post section to your second stage.
pipeline {
agent any
environment {
STAGE_2_EXECUTED = "0"
}
stages{
stage("....") {
steps {
....
}
}
stage("Second stage") {
when {
expression { /* Your condition */ }
}
steps {
....
}
post {
always {
script {
STAGE_2_EXECUTED = "1";
}
}
}
}
stage("Third Stage") {
steps {
script {
if(STAGE_2_EXECUTED == "1") {
input message: "Proceed or Abort", submitter: "user1,admin",
parameters: [string(name:'username', defaultValue: 'user1', description: 'Username of the user pressing Ok')]
}
}
build 'job5'
}
}
}
}

Conditional step based on currentBuild.result

I'd like a step to only be performed when the currentBuild.result is not set to UNSTABLE, this is immediately after a test step has been performed, I've managed to figure out that if this is not the case it gets set to null in my pipeline. Comparing a variable to "" should work when trying to determine that it is null, however this does not appear to work in my job step:
stage('Post test') {
when {
expression {
return (currentBuild.result == "")
}
}
steps {
Can someone please advise as to what I should be using in my conditional step expression.
Untested and not sure if this is exactly what you mean, but something like this?
stage('Post test') {
steps {
conditionalSteps {
condition {
status("Success","Success") # worst result, best result
}
steps {
shell("echo 'this is my command'")
}
}
}
}
If you want to run this at the end of a build you can just wrap it in a post. If you want to run a command while continuing the pipeline, I think you would have to wrap it in a script closure. This is untested, but I believe it will work for what you want:
stages {
stage('1') {}
stage('2') {}
stage('3') {
steps {
script {
if (currentBuild.result == "UNSTABLE") {
println "this should be unstable"
}
}
}
}
post {
unstable {
println "here be unstable"
}
}
}

Can I build stages with a function in a Jenkinsfile?

I'd like to use a function to build some of the stages of my Jenkinsfile. This is going to be a build with a number of repetitive stages/steps - I'd not like to generate everything manually.
I was wondering if it's possible to do something like this:
_make_stage() {
stage("xx") {
step("A") {
echo "A"
}
step("B") {
echo "B"
}
}
}
_make_stages() {
stages {
_make_stage()
}
}
// pipeline starts here!
pipeline {
agent any
_make_stages()
}
Unfortunately Jenkins doesn't like this - when I run I get the error:
WorkflowScript: 24: Undefined section "_make_stages" # line 24, column 5.
_make_stages()
^
WorkflowScript: 22: Missing required section "stages" # line 22, column 1.
pipeline {
^
So what's going wrong here? The function _make_stages() really looks like it returns whatever the stages object returns. Why does it matter whether I put that in a function call or just inline it into the pipeline definition?
As explained here, Pipeline "scripts" are not simple Groovy scripts, they are heavily transformed before running, some parts on master, some parts on slaves, with their state (variable values) serialized and passed to the next step. As such, every Groovy feature is not supported, and what you see as simple functions really is not.
It does not mean what you want to achieve is impossible. You can create stages programmatically, but apparently not with the declarative syntax. See also this question for good suggestions.
You can define a declarative pipeline in a shared library, for example:
// vars/evenOrOdd.groovy
def call(int buildNumber) {
if (buildNumber % 2 == 0) {
pipeline {
agent any
stages {
stage('Even Stage') {
steps {
echo "The build number is even"
}
}
}
}
} else {
pipeline {
agent any
stages {
stage('Odd Stage') {
steps {
echo "The build number is odd"
}
}
}
}
}
}
// Jenkinsfile
#Library('my-shared-library') _
evenOrOdd(currentBuild.getNumber())
See Defining Declarative Pipelines

Resources