Skip to content

Commit

Permalink
chore(jenkins): Include breadcrumb error message when jenkins job fai…
Browse files Browse the repository at this point in the history
…ls (spinnaker#4011)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
robzienert and mergify[bot] authored Nov 30, 2020
1 parent cc93ece commit ac71936
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ public void appendErrorMessage(String errorMessage) {
(List<String>) exceptionDetails.getOrDefault("errors", new ArrayList<String>());
exceptionDetails.putIfAbsent("errors", errors);

// Path: exception.details.errors
errors.add(errorMessage);

// This might be a no-op, but if there wasn't an exception object there, we should add it to the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.netflix.spinnaker.orca.igor.tasks

import com.google.common.base.Enums
import com.netflix.spinnaker.kork.core.RetrySupport
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus
import com.netflix.spinnaker.orca.api.pipeline.OverridableTimeoutRetryableTask
Expand All @@ -42,13 +43,6 @@ class MonitorJenkinsJobTask implements OverridableTimeoutRetryableTask {
@Autowired
RetrySupport retrySupport

private static Map<String, ExecutionStatus> statusMap = [
'ABORTED' : ExecutionStatus.CANCELED,
'FAILURE' : ExecutionStatus.TERMINAL,
'SUCCESS' : ExecutionStatus.SUCCEEDED,
'UNSTABLE': ExecutionStatus.TERMINAL
]

@Override
TaskResult execute(StageExecution stage) {
String master = stage.context.master
Expand All @@ -70,15 +64,23 @@ class MonitorJenkinsJobTask implements OverridableTimeoutRetryableTask {

outputs.buildInfo = build

if (statusMap.containsKey(result)) {
ExecutionStatus status = statusMap[result]
if (result == 'UNSTABLE' && stage.context.markUnstableAsSuccessful) {
status = ExecutionStatus.SUCCEEDED
}
return TaskResult.builder(status).context(outputs).outputs(outputs).build()
} else {
def jobStatus = Optional.ofNullable(result)
.map { Enums.getIfPresent(JenkinsJobStatus.class, it).orNull() }
.orElse(null)
if (jobStatus == null) {
return TaskResult.builder(ExecutionStatus.RUNNING).context([buildInfo: build]).build()
}

ExecutionStatus taskStatus = jobStatus.executionStatusEquivalent
if (jobStatus == JenkinsJobStatus.UNSTABLE && stage.context.markUnstableAsSuccessful) {
taskStatus = JenkinsJobStatus.SUCCESS.executionStatusEquivalent
}

if (jobStatus.errorBreadcrumb != null) {
stage.appendErrorMessage(jobStatus.errorBreadcrumb)
}

return TaskResult.builder(taskStatus).context(outputs).outputs(outputs).build()
} catch (RetrofitError e) {
if ([503, 500, 404].contains(e.response?.status)) {
log.warn("Http ${e.response.status} received from `igor`, retrying...")
Expand All @@ -88,4 +90,29 @@ class MonitorJenkinsJobTask implements OverridableTimeoutRetryableTask {
throw e
}
}

/**
* Maps Jenkins job statuses to {@link ExecutionStatus}
*/
static enum JenkinsJobStatus {
ABORTED(ExecutionStatus.CANCELED, "Job was aborted (see Jenkins)"),
FAILURE(ExecutionStatus.TERMINAL, "Job failed (see Jenkins)"),
SUCCESS(ExecutionStatus.SUCCEEDED, null),
UNSTABLE(ExecutionStatus.TERMINAL, "Job is unstable")

/**
* Orca's equivalent execution status value.
*/
ExecutionStatus executionStatusEquivalent

/**
* An optional error message breadcrumb that is surfaced to users when the jenkins job fails.
*/
String errorBreadcrumb

JenkinsJobStatus(ExecutionStatus executionStatusEquivalent, String errorBreadcrumb) {
this.executionStatusEquivalent = executionStatusEquivalent
this.errorBreadcrumb = errorBreadcrumb
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.netflix.spinnaker.orca.igor.tasks

import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus
import com.netflix.spinnaker.orca.clouddriver.pipeline.job.model.JobStatus
import com.netflix.spinnaker.orca.igor.BuildService
import com.netflix.spinnaker.orca.pipeline.model.PipelineExecutionImpl
import com.netflix.spinnaker.orca.pipeline.model.StageExecutionImpl
Expand Down Expand Up @@ -154,4 +155,32 @@ class MonitorJenkinsJobTaskSpec extends Specification {
false | ExecutionStatus.TERMINAL
null | ExecutionStatus.TERMINAL
}

@Unroll
def 'provides breadcrumb stage error message in failure states'() {
given:
def stage = new StageExecutionImpl(pipeline, "jenkins", [master: "builds", job: "orca", buildNumber: 4])

and:
task.buildService = Stub(BuildService) {
getBuild(stage.context.buildNumber, stage.context.master, stage.context.job) >> [result: jobState]
}

when:
task.execute(stage)

then:
if (expectedErrorMessage) {
stage.context.exception.details.errors.contains(expectedErrorMessage)
} else {
stage.context.exception == null
}

where:
jobState || expectedErrorMessage
MonitorJenkinsJobTask.JenkinsJobStatus.ABORTED || "Job was aborted (see Jenkins)"
MonitorJenkinsJobTask.JenkinsJobStatus.FAILURE || "Job failed (see Jenkins)"
MonitorJenkinsJobTask.JenkinsJobStatus.SUCCESS || false
MonitorJenkinsJobTask.JenkinsJobStatus.UNSTABLE || "Job is unstable"
}
}

0 comments on commit ac71936

Please sign in to comment.