Declarative jenkins pipeline do not abort on input - jenkins

Simple question I got input in declarative pipeline in jenkins. When I click abort on prompt I do not want it to mark build as aborted. To prevent answers that stack already have, I am looking for solution in declarative pipeline, without escaping to scripting.
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
input 'Deploy to UAT?'
deploy()
}
post {
aborted {
script {
//Throws exception(not allowed to use rawBuild)
currentBuild.rawBuild.#result = hudson.model.Result.SUCCESS
//Do not change status because it can only be worse
currentBuild.result = 'SUCCESS'
//Do not change status because it can only be worse
currentBuild.currentResult = 'SUCCESS'
}
}
}

I don't think it's possible to not abort pipeline with the simple input field since that the purpose of it.
What You could do is to use checkbox in the input like
def deployToUat
steps {
script {
deployToUat = input(
id: 'Proceed', message: 'Deploy to UAT?', parameters: [
[$class: 'BooleanParameterDefinition', defaultValue: true, description: '', name: 'Proceed with deployment?']
])
}
}
stage('UAT deployment') {
when {
expression { deployToUat == true }
}
steps {
deploy()
}
}

Well, you could do
script {
try {
input 'Deploy to UAT?'
} catch(err) {
currentBuild.result = 'SUCCESS'
return
}
}
I guess the above is the only correct way since the result can only worsen.
public void setResult(#Nonnull Result r) {
if (state != State.BUILDING) {
throw new IllegalStateException("cannot change build result while in " + state);
}
// result can only get worse
if (result==null || r.isWorseThan(result)) {
result = r;
LOGGER.log(FINE, this + " in " + getRootDir() + ": result is set to " + r, LOGGER.isLoggable(Level.FINER) ? new Exception() : null);
}
}

Related

Jenkinsfile exit a parallel stage when parameter is empty

I have the belo Jenkinsfile where I take user input for one and/or two parallel steps/stages. If the user input is empty, how can I exit that step/stage?
For example, if the user didn't enter any value for firstTask_build_number I need to echo "firstTask_build_number is empty - aborting firstTask!" and exit that. How can I exit safely?
pipeline {
agent any
parameters {
string(name: 'firstTask_build_number', defaultValue: '', description: 'Build ID of firstTask')
string(name: 'secondTask_build_number', defaultValue: '', description: 'Build ID of secondTask')
}
stage("Parallel") {
steps {
parallel (
"firstTask" : {
if (firstTask_build_number.size() == 0) {
echo "firstTask_build_number is empty - aborting firstTask!"
}
else {
//do some stuff
}
},
"secondTask" : {
if (secondTask_build_number.size() == 0) {
echo "secondTask_build_number is empty - aborting secondTask!"
}
else {
//do some stuff
}
}
)
}
}
}
You can just return out of it.
parallel (
"firstTask" : {
if (firstTask_build_number.size() == 0) {
echo "firstTask_build_number is empty - aborting firstTask!"
return
}
else {
//do some stuff
}
}
)

Jenkinsfile fail a step and continue the rest

I have a Jenkinsfile like this:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '7'))
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('Admin') {
steps {
script {
try {
result = "FAIL"
} catch(error) {
result = "FAIL"
}
}
}
}
stage('Normal') {
steps {
script {
try {
sh("echo 'hi'")
} catch(error) {
}
}
}
}
}
}
How do I fail the first step and have it show red and have the pipeline continue for more steps?
I have looked at all the SO answers for this and can't make it work.
Setting result = "FAIL" does not cause the step to fail. How can I do this but continue the next step(s)?
You can mark a build status as failed by setting a new value for the global jenkins variable currentBuild. The variable has 3 states: SUCCESS (green), FAILURE (red) and UNSTABLE (yellow). The catch should prevent the build from exiting so you can set the status of the currentBuild and simply continue.
try {
//so something
} catch(error) {
//mark build as failed
currentBuild.result = 'FAILURE'
}
}
I think this should work. Sadly I don't have a way to validate this atm. If it does not work, please tell me.
As per my knowledge we can implement same as below.
Use script step and try-catch block
stage('someStage') {
steps {
script {
try {
//Do something
} catch (err) {
echo err
}
}
echo currentBuild.result = 'FAILURE'
}
}
You can use propagate: false which is available in build step. Somethng like below
stage("example") {
b = build(job: "example-job", propagate: false).result
if(b == 'FAILURE') {
echo "First job failed"
currentBuild.result = 'FAILURE' // of FAILURE
}
}
stage("test") {
build("test-job")
}
Links :
https://jenkins.io/doc/pipeline/steps/pipeline-build-step/
https://medium.com/#Lenkovits/jenkins-pipelines-and-their-dirty-secrets-1-9e535cd603f4
Do you want to show one stage "red" and all other (not failing) "green"?
As far as I know, that is not possible.
Do you want to know which stage is failing and which one runs correctly?
Well, it depends how you want it to be displayed
The easy way:
as suggested in a previous answer, you can use "echo" to print it in the console
The 'I want it full power' way:
capsulate a try-catch inside another try-catch and use the "error" signal:
stages {
try {
stage ('Normal') {
try {
/* Do your thing */
} catch (err) {
/* Mark this stage as "failure" using error
error 'Stage Normal failing'
}
} // stage 'Normal'
} catch (err) {
// here is the 'error' signal captured and omitted by now
}
Stage failing appears with the text "failed" shown in the box and in a darker red

Access which stage failed in previous Jenkins build

I have written a Jenkinsfile script which gets whether documents are updated or code is updated in the current Github commit and starts all the stages accordingly. If only documents are updated I don't run the code testing stage again.
So now if the previous build failed and now in the current Git commit only documents are updated then it will not run the code testing stage. So I want a method/way to know which stage failed during the last Jenkins build and if needed run the current Jenkins build.
For example if the code testing stage failed in the previous build, I'll need to run the code testing stage for this build, otherwise I can just run the documents zipping stage.
As a workaround to get failed stages from Jenkins build such function can be used. I could not find a simpler way to do it. But this code requires to run without Groovy sandbox or you need to whitelist a lot of Jenkins method signatures (which is not recommeded). Also blueocean plugin has to be installed.
import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor
import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper
import org.jenkinsci.plugins.workflow.flow.FlowExecution
import org.jenkinsci.plugins.workflow.graph.FlowNode
import org.jenkinsci.plugins.workflow.job.WorkflowRun
#NonCPS
List getFailedStages(WorkflowRun run) {
List failedStages = []
FlowExecution exec = run.getExecution()
PipelineNodeGraphVisitor visitor = new PipelineNodeGraphVisitor(run)
def flowNodes = visitor.getPipelineNodes()
for (node in flowNodes) {
if (node.getType() != FlowNodeWrapper.NodeType.STAGE ) { continue; }
String nodeName = node.getDisplayName()
def nodeResult = node.getStatus().getResult()
println String.format('{"displayName": "%s", "result": "%s"}',
nodeName, nodeResult)
def resultSuccess = io.jenkins.blueocean.rest.model.BlueRun$BlueRunResult.SUCCESS
if (nodeResult != resultSuccess) {
failedStages.add(nodeName)
}
}
return failedStages
}
// Ex. Get last build of "test_job"
WorkflowRun run = Jenkins.instance.getItemByFullName("test_job")._getRuns()[0]
failedStages = getFailedStages(run)
I thing it could fit. Use buildVariables from previous build, timeout \ input in case You need to change something, try \ catch for setup stages status. Code example:
// yourJob
// with try/catch block
def stageOneStatus;
def stageTwoStatus;
def stageThreeStatus;
pipeline {
agent any
stages {
stage("STAGE 1") {
// For initial run every stage
when { expression { params.stageOne == "FAILURE" } }
steps {
script {
try {
// make thing
} catch (Exception e) {
stageOneStatus = "FAILURE";
}
}
}
}
stage("STAGE 2") {
when { expression { params.stageTwo == "FAILURE" } }
steps {
script {
try {
// make thing
} catch (Exception e) {
stageTwoStatus = "FAILURE";
}
}
}
}
stage("STAGE 3") {
when { expression { params.stageThree == "FAILURE" } }
steps {
script {
try {
// make thing
} catch (Exception e) {
stageThreeStatus = "FAILURE";
}
}
}
}
}
}
// Checking JOB
def pJob;
pipeline {
agent any
stages {
// Run job with inheriting variable from build
stage("Inheriting job") {
steps {
script {
pJob = build(job: "yourJob", parameters: [
[$class: 'StringParameterValue', name: 'stageOne', value: 'FAILURE'],
[$class: 'StringParameterValue', name: 'stageTwo', value: 'FAILURE'],
[$class: 'StringParameterValue', name: 'stageThree', value: 'FAILURE']
], propagate: false)
if (pJob.result == 'FAILURE') {
error("${pJob.projectName} FAILED")
}
}
}
}
// Wait for fix, and re run job
stage ('Wait for fix') {
timeout(time: 24, unit: 'HOURS') {
input "Ready to rerun?"
}
}
// Re run job after changes in code
stage("Re-run Job") {
steps {
script {
build(
job: "yourJob",
parameters: [
[$class: 'StringParameterValue',name: 'stageOne',value: pJob.buildVariables.stageOneStatus ],
[$class: 'StringParameterValue',name: 'stageTwo',value: pJob.buildVariables.stageTwoStatus ],
[$class: 'StringParameterValue',name: 'stageThree',value: pJob.buildVariables.stageThreeStatus ]
]
)
}
}
}
}
}

Why am I not seeing terminal output in the Build Console for this Jenkinsfile?

I'm a little confused as to why I am not seeing the output from the echo() statements in the jenkinsSlack() function in this Jenkinsfile. The stages are definitely running as I can see them executing in the Pipeline visualization.
#!groovy
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
// simplify the generation of Slack notifications for start and finish of Job
def jenkinsSlack(type, channel=slack_channel()) {
echo("echo 'In jenkinsSlack()...")
echo("echo 'type specified as : ${type}'")
echo("echo 'channel specified as : ${channel}'")
if ( 'SUCCESS' == currentBuild.result ) {
slackSend channel: channel, color: 'good', message: "type: ${type}"
} else if ( 'FAILURE' == currentBuild.result ) {
slackSend channel: channel, color: 'danger', message:"type: ${type}"
} else {
slackSend channel: channel, color: 'warning', message: "type: ${type}"
}
// node - action starts here
node {
wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm', 'defaultFg': 2, 'defaultBg':1]) {
stage ("send Slack start message") {
checkout scm
jenkinsSlack('start')
}
stage("send Slack finish message") {
jenkinsSlack('finish')
}
} // AnsiColorBuildWrapper
}
Thx
Echo messages are missing because jenkinsSlack(type, channel=slack_channel()) just returns the value of slack_channel() without executing the method body including echo.
This is Jenkins specific problem related to CPS transform. Jenkins pipeline script is based on groovy but it has some constraints with regard to its syntax and usage. See more details here: https://github.com/jenkinsci/workflow-cps-plugin/blob/master/README.md#technical-design
Possible workarounds below.
1.Use #NonCPS annotation for slack_channel()
#NonCPS
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
2.determine SLACK_CHANNEL in advance and pass it to the default argument channel:
def slack_channel() {
try { if ('' != SLACK_CHANNEL) { return SLACK_CHANNEL } }
catch (MissingPropertyException e) { return '#nathan-webhooks' }
}
SLACK_CHANNEL = slack_channel()
def jenkinsSlack(type, channel=SLACK_CHANNEL) {
echo type
echo channel
}

Show a Jenkins pipeline stage as failed without failing the whole job

Here's the code I'm playing with
node {
stage 'build'
echo 'build'
stage 'tests'
echo 'tests'
stage 'end-to-end-tests'
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
?????? I want to just fail this stage
}
}
Is there any way for me to mark the 'end-to-end-tests' stage as failed without failing the whole job? Propagate false just always marks the stage as true, which is not what I want, but Propagate true marks the job as failed which I also don't want.
This is now possible, even with declarative pipelines:
pipeline {
agent any
stages {
stage('1') {
steps {
sh 'exit 0'
}
}
stage('2') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh "exit 1"
}
}
}
stage('3') {
steps {
sh 'exit 0'
}
}
}
}
In the example above, all stages will execute, the pipeline will be successful, but stage 2 will show as failed:
As you might have guessed, you can freely choose the buildResult and stageResult, in case you want it to be unstable or anything else. You can even fail the build and continue the execution of the pipeline.
Just make sure your Jenkins is up to date, since this is a fairly new feature.
Stage takes a block now, so wrap the stage in try-catch. Try-catch inside the stage makes it succeed.
The new feature mentioned earlier will be more powerful. In the meantime:
try {
stage('end-to-end-tests') {
node {
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
result = "FAIL" // make sure other exceptions are recorded as failure too
}
stage('deploy') {
if (result.equals("SUCCESS")) {
build 'deploy'
} else {
echo "Cannot deploy without successful build" // it is important to have a deploy stage even here for the current visualization
}
}
Sounds like JENKINS-26522. Currently the best you can do is set an overall result:
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
currentBuild.result = e2e.result
// but continue
}
I recently tried to use vaza's answer
Show a Jenkins pipeline stage as failed without failing the whole job as template for writing a function that excutes a job in an own stage named like the job name. Surprisingly it worked, but maybe some groovy experts have a look at it :)
Here is how it looks like if one of the jobs is aborted:
def BuildJob(projectName) {
try {
stage(projectName) {
node {
def e2e = build job:projectName, propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
error 'FAIL' //sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
currentBuild.result = 'UNSTABLE'
result = "FAIL" // make sure other exceptions are recorded as failure too
}
}
node {
BuildJob('job1')
BuildJob('job2')
}
In order to show a successful build with a failed stage when a downstream job fails AND support a user being able to cancel a build (including all subsequent stages), I had to use a combination of various solutions, specifically when, try/catch, throw and catchError().
env.GLOBAL_BUILD_ABORTED = false // Set if the user aborts the build
pipeline {
agent any
stages {
stage('First Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #1, build #1')
myLocalBuildMethod('Stage #1, build #2')
}
}
}
stage('Second Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #2, build #1')
myLocalBuildMethod('Stage #2, build #2')
myLocalBuildMethod('Stage #2, build #3')
}
}
}
}
}
def myLocalBuildMethod(myString) {
/* Dummy method to show User Aborts vs Build Failures */
echo "My Local Build Method: " + myString
try {
build (
job: "Dummy_Downstream_Job"
)
} catch (e) {
/* Build Aborted by user - Stop All Test Executions */
if (e.getMessage().contains("was cancelled") || e.getMessage().contains("ABORTED")) {
env.GLOBAL_BUILD_ABORTED = true
}
/* Throw the execiption to be caught by catchError() to mark the stage failed. */
throw (e)
}
// Do other stuff...
}
You could add a explicit fail task, such as 'sh "not exist command"' in the stage.
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
try {
sh "not exist command"
}catch(e) {
}
}
Solution steps
You must emit an error in a stage to mark it as an error
Outside the scope of the stage, handle the exception and choose the build status
This makes the effect desired by a couple of users here, including myself, #user3768904, #Sviatlana
Success with failed Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'SUCCESS'
return
}
stage("Skipped") {
// This stage will never run
}
}
Aborted with failure Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'ABORTED'
return
}
stage("Skipped") {
// This stage will never run
}
}
You can use the following code in your else statement:
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
error "some err msg"
}
This could be a general pattern showing how to customize the stage result with nice messages using the built-in functions and propagate the sub-job's result to the stage result. That the overall build is marked unstable if a sub-job is not successful is just a implementation choice for this example.
def run_sub_job() {
def jobBuild = build(job: 'foo', wait: true, propagate: false)
def result = jobBuild.getResult()
def msg = 'sub-job: ' + result
if ('SUCCESS' == result) {
println(msg)
} else if ('UNSTABLE' == result) {
unstable(msg) // will also set buildResult to UNSTABLE
} else { // anything else (FAILURE, ABORTED ...) is considered an error
catchError(
buildResult: 'UNSTABLE',
stageResult: result // propagate sub-job result
) {
error(msg)
}
}
}

Resources