Skip to content

Instantly share code, notes, and snippets.

@brianoflan
Last active November 14, 2018 21:38
Show Gist options
  • Save brianoflan/9d2a21f82904b48d1be2817ac32fd11c to your computer and use it in GitHub Desktop.
Save brianoflan/9d2a21f82904b48d1be2817ac32fd11c to your computer and use it in GitHub Desktop.
Exhaustive Jenkinsfile
#!/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