Skip to content

Instantly share code, notes, and snippets.

@CoRfr
Created November 22, 2020 04:02
Show Gist options
  • Save CoRfr/b0c064c578e19133ed180209167f3ba3 to your computer and use it in GitHub Desktop.
Save CoRfr/b0c064c578e19133ed180209167f3ba3 to your computer and use it in GitHub Desktop.
Jenkins pipeline build() function that uses results from build-failure-analyzer plugin to automatically retrigger builds
import hudson.AbortException
import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction
import org.jvnet.jenkins.plugins.nodelabelparameter.LabelParameterValue
import org.jvnet.jenkins.plugins.nodelabelparameter.node.AllNodeEligibility
import hudson.model.Label
import hudson.model.labels.LabelExpression
import jenkins.model.Jenkins
import com.cloudbees.groovy.cps.NonCPS
@NonCPS
def foundFailureCauses(bld, ignoreCauses=[]) {
// Failure, try to find failure cause
def failureCause = bld.rawBuild.getAction(FailureCauseBuildAction.class)
List<String> categories = []
if(failureCause)
{
def causes = failureCause.foundFailureCauses
for (def c = 0; c < causes.size(); c++)
{
def cause = causes[c]
def ignore = false
def ignoreState = ""
if(ignoreCauses.contains(cause.name))
{
ignore = true
ignoreState = "(ignored)"
}
List<String> causeCategories = []
if(cause.categories)
{
for (def cat in cause.categories)
{
if (cat && !(cat in causeCategories))
{
causeCategories.add(cat)
if (!ignore && !(cat in categories))
{
categories.add(cat)
}
}
}
}
println "${cause.name}: <${String.join(" ", causeCategories)}> ${cause.description} ${ignoreState}"
}
}
return String.join(" ", categories)
}
def call(LinkedHashMap config) {
def bld = null
def exception = null
def job = config['job']
def i
// Propagate by default
def propagate = config.get('propagate', true)
//config['propagate'] = true
// Do not keep the build forever by default
def keep = config.get("keep", false)
config.remove("keep")
// Number of times we should retry if the build failed
// with a 'retrigger' failure cause
def retryNb = config.get("maxRetry", 3)
config.remove("maxRetry")
// Enable to mechanism to retry by default
def retry = config.get("retry", true)
config.remove("retry")
if (!retry)
{
retryNb = 1
}
// List of failure causes that should be consider valid to retrigger
def ignoreCauses = config.get("ignoreCauses", [])
config.remove("ignoreCauses")
def updateNodeLabel = null
def bldNodeLabel = null
for (i = 0; i < retryNb; i++) {
bld = null
try {
def updateConfig = config.clone()
if (updateNodeLabel && bldNodeLabel)
{
if (checkNode(job, bldNodeLabel))
{
updateConfig['parameters'] += updateNodeLabel
}
}
bld = build(updateConfig)
if (bld && bld.getBuildVariables())
{
def bldEnvVars = bld.getBuildVariables()
def bldNodeName = bldEnvVars["NODE_NAME"]
def jobUrl = bldEnvVars["JOB_URL"]
if (bldEnvVars["NODE_LABEL"])
{
bldNodeLabel = bldEnvVars["NODE_LABEL"]
}
else
{
def jobConfigData = Jenkins.instance.getJob(job)
if (jobConfigData.hasProperty("assignedLabelString"))
{
bldNodeLabel = jobConfigData.assignedLabelString
}
}
// For job that does not have NODE_LABEL parameter or assignedNode in config.xml:
// We check the case that current node name is power node
// because almost jobs run on power node and infrastructure issues mostly happen here.
if (!bldNodeLabel && bldNodeName && bldNodeName.contains("power"))
{
bldNodeLabel = "power"
}
if (bldNodeLabel && (bldNodeLabel != "master"))
{
bldNodeLabel = "(${bldNodeLabel}) && !${bldNodeName}"
updateNodeLabel = new LabelParameterValue("NODE_LABEL",
bldNodeLabel,
false, // allNodesMatchingLabel
new AllNodeEligibility())
}
}
}
catch(org.jenkinsci.plugins.workflow.steps.FlowInterruptedException ex)
{
exception = ex
def cause = null
if (ex.causes.size() > 0)
{
cause = ex.causes.first()
}
if (cause instanceof org.jenkinsci.plugins.workflow.support.steps.build.DownstreamFailureCause)
{
bld = new org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper(cause.downstreamBuild, false)
}
else
{
println "Unable to determine underlying build from ${cause}"
if (cause instanceof org.jenkinsci.plugins.workflow.support.steps.build.BuildTriggerCancelledCause)
{
println "BuildTriggerCancelledCause: ${cause.cause}"
}
if (propagate == true)
{
throw ex // Rethrow
}
}
}
if(keep)
{
if(bld && bld.rawBuild)
{
bld.rawBuild.keepLog(true)
}
else if(config['wait'] != false)
{
println "Build '${job}': Unable to keep logs as build object is null."
}
}
if(config['wait'] == false)
{
println "Build '${job}': Not waiting"
return bld
}
if(!bld)
{
println "Build '${job}': Unable to determine build object"
if (!exception)
{
exception = new AbortException("Build '${job}': Unable to determine build object")
}
// Even if we were told not to propagate, we do since we don't really have a choice
throw exception
}
if( (bld.result == "SUCCESS")
|| (bld.result == "UNSTABLE"))
{
return bld
}
def categories = foundFailureCauses(bld, ignoreCauses)
if(!categories.contains("retrigger") || (bld.result == "ABORTED"))
{
break
}
println "Build '${job}': #${bld.number} failed (${bld.result}), but retriggering (${i+1}/${retryNb})."
}
// Since this is the last try, print the failure message so it can appear
// at a higher level
foundFailureCauses(bld, ignoreCauses)
def message = "Build '${job}': #${bld.number} failed (${bld.result})"
if(i != 0)
{
message += ", after ${i} tries."
}
if((propagate == true) && (bld.result != "SUCCESS")
&& (bld.result != "UNSTABLE"))
{
def abortEx = new AbortException(message)
if (exception)
{
abortEx.initCause(exception)
}
throw abortEx
}
println message
return bld
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment