Last active
November 14, 2018 21:38
-
-
Save brianoflan/9d2a21f82904b48d1be2817ac32fd11c to your computer and use it in GitHub Desktop.
Exhaustive Jenkinsfile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env groovy | |
CLEAN = true | |
DEBUG = 2 | |
OVERALL_TIMEOUT = 1 // minutes | |
email_to = '[email protected], [email protected], ' | |
git_creds = 'general-deploy-key-1' | |
git_url = '[email protected]:someNamespace/someProject.git' | |
branch = '' | |
default_branch = 'master' // Ignored if Jenkinsfile is pulled from SCM - in favor of that Jenkinsfile's SCM branch. | |
build_node = '' // Blank for default build server / slave node. | |
build_archive = "git/logs/**" | |
build_creds = [ | |
// AWS access key and secret | |
[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'aws-s3-creds-1', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY'], | |
// Secret file | |
file(credentialsId: 'a-secret-file-creds-id', variable: 'secret_file2'), | |
// Secret text | |
string(credentialsId: 'a-secret-text-creds-id', variable: 'secret_text3'), | |
// Username and password (conjoined) | |
usernameColonPassword(credentialsId: 'un-pw-creds-id-4', variable: 'conjoined_un_pw'), | |
// Username and password (separated) | |
usernamePassword(credentialsId: 'un-pw-creds-id-5', passwordVariable: 'pw5', usernameVariable: 'un5'), | |
] | |
git_timeout = 30 // seconds | |
git_sleep = 1 | |
git_patience1 = 2 | |
git_patience2 = 2 | |
disable_email = false | |
def prebuild() { | |
true | |
} | |
properties([ | |
disableConcurrentBuilds(), | |
]) | |
stage('overall') { timeout(time: OVERALL_TIMEOUT, unit: 'MINUTES') { node(build_node) { | |
currentBuild.result = "SUCCESS" | |
try { | |
if ( CLEAN ) { stage('clean') { clean() } } | |
stage('checkout') { | |
sh 'pwd ; hostname' | |
checkout() | |
} // END: stage('checkout') | |
stage('checkout on another node') { // This works. | |
node() { | |
sh 'pwd ; hostname' | |
checkout() | |
} // END: node | |
} // END: stage('checkout on another node') | |
stage('build') { | |
withCredentials(build_creds) { | |
try { timeout(time: 10, unit: 'SECONDS') { | |
prebuild() | |
} } catch(e) { | |
echo "Caught prebuild() error: '${e}'. Carrying on." | |
} | |
sh '[[ ! -e build.sh ]] || bash build.sh' | |
archive includes: build_archive | |
} | |
} | |
} catch (err) { | |
echo "Overall: Caught exception {${err}}." | |
well_known = catchy(err) | |
if ( ! isStringBlankOrNull(well_known) ) { | |
error = well_known | |
betterError("Caught well-known error: ${well_known} (${err})") | |
} | |
currentBuild.result = "FAILURE" | |
if ( !disable_email ) { notifyFailed("${err}") } | |
// throw err | |
betterError("Caught overall exception/error: {${err}}.") | |
} // END: try catch | |
} } } // END: node, timeout, and stage('overall') | |
// // // | |
def betterError(String s) { | |
def msg = "ERROR: ${s}." | |
echo msg | |
error msg | |
} | |
def catchy(e) { | |
result = '' | |
echo "catchy( e = '${e}')" | |
if ( e instanceof java.lang.NullPointerException && "${e}" =~ /\QCannot invoke method call() on null object\E/ ) { | |
echo """Caught an exception but it may be that we caught an explicit call to the 'error' keyword. | |
In this case the String argument to 'error' is lost and replaced with a NPE exception | |
(this exception, '${e}', | |
matches that usual vague pattern). | |
There is no way to recover or print that String argument. Sorry. | |
Consider preceding all 'error' calls with 'echo' to make sure the error is displayed | |
even if it gets caught in a 'try'/'catch' block. | |
Or define an alternate function like so: | |
def betterError(String s) { | |
def msg = "ERROR: \${s}." | |
echo msg | |
error msg | |
} | |
Such a custom function seems to be immune to the problem of try/catch block | |
gobbling up an 'error' keyword. | |
(That may actually print it twice - once with 'echo' and again when the caught | |
exception is printed, depending on what the 'catch' clause does with it.) | |
If you don't want to litter your build console output with the 'echo', you can | |
wrap it in an 'if': | |
if ( ! "\${env.VERBOSE}" =~ /^\$/ ) { echo msg } | |
""" | |
result = "Maybe caught _error_ keyword NPE." | |
} else if ( e instanceof java.lang.InterruptedException || | |
( e instanceof hudson.AbortException && "${e}" ==~ /\Qhudson.AbortException: script returned exit code 143\E/ ) | |
) { | |
result = "Caught timeout or user interrupt. Stopping." | |
betterError(result) | |
} | |
return result | |
} | |
def checkout() { | |
sh '[[ -d git ]] || mkdir git' | |
dir('git') { | |
def _branch = getBranch() | |
for (int i = 0; i <= git_patience1 + git_patience2; i++) { // NOTE: Unusual but proper '<=' logic. | |
def error = null | |
// | |
if ( i < git_patience1 + git_patience2) { | |
try { timeout(time: git_timeout, unit: 'SECONDS') { | |
if ( i < git_patience1 ) { | |
checkout scm // Works when Jenkinsfile is fetched immediately from source control. | |
} else { | |
// Works when Jenkinsfile is just pasted into a Jenkins pipeline job's Jenkinsfile text block. | |
git poll:true, url: git_url, branch: _branch, credentialsId: git_creds | |
echo "Using git branch '${_branch}'." | |
} | |
if ( DEBUG > 1 ) { debug_git() } | |
} } catch(e) { | |
well_known = catchy(e) | |
error = e | |
if ( ! isStringBlankOrNull(well_known) ) { | |
error = well_known | |
betterError("Caught well-known error: ${well_known} (${e})") | |
} | |
echo "Caught error, {${e}}. Trying again." | |
} | |
} else { | |
betterError("Caught too many errors trying to fetch from source control. Giving up.") | |
} | |
if ( ! error ) { break } | |
sh "sleep ${git_sleep}" | |
// | |
} // END: for(int i...) | |
} | |
} | |
def clean() { | |
sh 'pwd' | |
sh 'ls -A | while read x ; do rm -rf "$x" ; done' | |
sh 'ls -lart ; pwd' | |
} | |
def debug_git() { | |
echo "Using git commit:" | |
sh 'git rev-parse HEAD' | |
echo "Using git branch: '${getBranch()}'." | |
sh 'git rev-parse --abbrev-ref HEAD' | |
sh 'env | grep -i git || true' | |
sh 'env | grep -i branch || true' | |
} | |
def getBranch() { | |
def result = '' ; | |
// Variables 'branch' and 'default_branch' should already be defined as script globals. | |
try { | |
if (isStringBlankOrNull(branch)) { | |
// Might work if this is a multi-branch pipeline job kind. | |
timeout(time: 1, unit: 'SECONDS') { | |
branch = BRANCH_NAME | |
} | |
} | |
} catch ( e ) { | |
echo "Caught exception/error trying to use variable BRANCH_NAME: {${e}}. Trying other ways to decide branch name." | |
} | |
if (isStringBlankOrNull(branch)) { | |
// Works if this is a multi-branch pipeline job kind. | |
branch = env.BRANCH_NAME | |
} | |
try { | |
// if (branch == null || ( 0 == branch.compareTo(""))) { | |
if (isStringBlankOrNull(branch)) { | |
if (fileExists('.git')) { | |
// Works if this workspace has been used before and remembers its branch | |
branch = sh(returnStdout: true, script: "git rev-parse --abbrev-ref HEAD | egrep -v '^HEAD\$'").trim() | |
echo "Obtained branch from 'git rev-parse --abbrev-ref HEAD': '${branch}'." | |
} | |
} | |
} catch ( e ) { | |
echo "Caught exception/error trying to 'git rev-parse --abbrev-ref HEAD': {${e}}. Trying other ways to decide branch name." | |
} | |
result = (branch != null && ! ( 0 == branch.compareTo(""))) ? branch : default_branch | |
return result | |
} | |
def isStringBlankOrNull(s) { | |
if ( s == null ) { | |
return true | |
} else if ( 0 == s.compareTo("") ) { | |
return true | |
} | |
return false | |
} | |
def notifyFailed(report) { | |
// step([$class: 'Mailer', recipients: '[email protected]']) | |
emailext( | |
// to: '[email protected], ', | |
to: email_to, | |
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']], | |
subject: "[${env.JOB_NAME}] build #${env.BUILD_NUMBER} failed", | |
body: """ | |
There was an error. | |
Full report: | |
${report} | |
See ${env.BUILD_URL} or ${env.BUILD_URL}console for more information. | |
""", | |
) | |
} | |
// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment