Created
November 22, 2020 04:02
-
-
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
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
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