Skip to content

Instantly share code, notes, and snippets.

@beercan1989
Last active October 3, 2023 14:03
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save beercan1989/b66b7643b48434f5bdf7e1c87094acb9 to your computer and use it in GitHub Desktop.
Save beercan1989/b66b7643b48434f5bdf7e1c87094acb9 to your computer and use it in GitHub Desktop.
Retry, Continue or Abort (Jenkins Pipeline) with Colour Support

Create test Jenkins

docker run -it --rm --name jenkins -p '8080:8080' jenkins:alpine

Install

  • Login as Admin
  • Accept the standard plugins
  • Continue as Admin
  • Install AnsiColor plugin
  • Create Pipeline and enter config in pipeline.groovy
  • Run job and see no executors used and more than two can be created and running (well paused)
//
// Coloured Messages: http://testerfenster.com/blog/jenkins-tutorials-add-color-to-console-output/
//
String boldGreenMessage(final String message) { return "\033[1;32m${message}\033[0m" }
String boldBlueMessage(final String message) { return "\033[1;34m${message}\033[0m" }
String boldRedMessage(final String message) { return "\033[1;31m${message}\033[0m" }
String boldYellowMessage(final String message) { return "\033[1;33m${message}\033[0m" }
String triplePrefixMessage(final Closure<String> colour, final String prefix, final String message) {
def colouredPrefix = "${colour("${prefix}")}"
return "${colouredPrefix}\n${colouredPrefix} ${message}\n${colouredPrefix}"
}
void successMessage(final String message) { ansiColor('xterm') { echo triplePrefixMessage(this.&boldGreenMessage, '[SUCCESS]', message) } }
void infoMessage(final String message) { ansiColor('xterm') { echo triplePrefixMessage(this.&boldBlueMessage, '[INFO]', message) } }
void warningMessage(final String message) { ansiColor('xterm') { echo triplePrefixMessage(this.&boldYellowMessage, '[WARNING]', message) } }
void errorMessage(final String message) { ansiColor('xterm') { echo triplePrefixMessage(this.&boldRedMessage, '[ERROR]', message) } }
//
// Retry, Continue or Abort on errors
//
public <R> R retryContinueOrAbort(final Closure<R> action, final int count = 0) {
infoMessage "Trying action, attempt count is: ${count}"
try {
return action.call();
} catch (final exception) {
errorMessage exception.toString()
def userChoice
timeout(time: 30, unit: 'MINUTES') {
userChoice = input(
message: 'Something went wrong, what do you want to do next?',
parameters: [
choice(
name: 'Next Action',
choices: ['Retry', 'Continue', 'Abort'].join('\n'),
description: 'Whats your next action?'
)
]
)
}
switch (userChoice) {
case 'Retry':
warningMessage 'User has opted to try the action again.'
return retryContinueOrAbort(action, count + 1)
case 'Continue':
warningMessage 'User has opted to continue past the action, they must have manually fixed things.'
return null;
default:
errorMessage 'User has opted to abort the action'
throw exception;
}
}
};
//
// Test Pipeline Script
//
pipeline {
agent none
stages {
stage('Reach the bridge') {
steps {
infoMessage 'Stop. Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.'
}
}
stage('Answer a question') {
steps {
script {
favouriteColour = retryContinueOrAbort {
infoMessage 'What... is your favourite colour?'
error 'Blue. No, yel... [he is also thrown over the edge] auuuuuuuugh.'
}
}
}
}
stage('Get POM Version') {
steps {
successMessage 'You have made it past the bridge keeper!'
script {
mavenVersion = retryContinueOrAbort {
"0.0.1-SNAPSHOT"
}
}
}
}
stage('Print Variables') {
steps {
successMessage "favouriteColour: ${favouriteColour}"
successMessage "mavenVersion: ${mavenVersion}"
}
}
}
}
@gianpaolof
Copy link

hello. thank you for sharing this gist. since I'm not a groovy expert, I am not sure I'm doing the right things.
I have a script that calls Fastlane, I want to use your function and try to run my script twice in the following way:

                def myscript= sh(script: "fastlane ${params.BUILD_LANE}", returnStdout: true)

                retryContinueOrAbort {
                    myscript 2
                }

Is that correct?

@beercan1989
Copy link
Author

@gianpaolof depending on what your trying to achieve, that's not that the retryCotninueorAbort script it trying to achieve. As it will try running the passed code block, if it fails it will prompt you as to whether you want to retry it, continue as if nothing had failed or give up.

The main purpose was to provide a means of manually fixing things that went wrong during a deployment, that couldn't be automatically retired and once fixed you could just continue pass.

If you are after automatic retry on failure, up to a certain amount I believe the built in retry function might do what you want. https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#retry-retry-the-body-up-to-n-times

retry(count: 2) {
    def standardOut = sh(script: "fastlane ${params.BUILD_LANE}", returnStdout: true)
}

That said if you want it to always run the command twice, native groovy loops should just work these days with the latest versions.

(1..2).each {
    def standardOut = sh(script: "fastlane ${params.BUILD_LANE}", returnStdout: true)
}

Now I've written this out, there is also probably a bug in your code snippet anyway as the sh command will execute and assign the output to myscript before being passed into retryContinueOrAbort and attempted to be treated as a function/method/closure.

So it could look like this and work, in theory.

def myscript = {
    return sh(script: "fastlane ${params.BUILD_LANE}", returnStdout: true)
}

retryContinueOrAbort {
    def standardOut = myscript()
}

Disclaimer this is all written off the top of my head plus a little bit of a Google to refresh on syntax.

@gianpaolof
Copy link

thank you very much, learned something new and very helpful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment