| package cloud |
| |
| import ( |
| "context" |
| "fmt" |
| "strings" |
| |
| "github.com/hashicorp/go-tfe" |
| ) |
| |
| type taskResultSummary struct { |
| unreachable bool |
| pending int |
| failed int |
| failedMandatory int |
| passed int |
| } |
| |
| type taskStageReadFunc func(b *Cloud, stopCtx context.Context) (*tfe.TaskStage, error) |
| |
| func summarizeTaskResults(taskResults []*tfe.TaskResult) *taskResultSummary { |
| var pendingCount, errCount, errMandatoryCount, passedCount int |
| for _, task := range taskResults { |
| if task.Status == "unreachable" { |
| return &taskResultSummary{ |
| unreachable: true, |
| } |
| } else if task.Status == "running" || task.Status == "pending" { |
| pendingCount++ |
| } else if task.Status == "passed" { |
| passedCount++ |
| } else { |
| // Everything else is a failure |
| errCount++ |
| if task.WorkspaceTaskEnforcementLevel == "mandatory" { |
| errMandatoryCount++ |
| } |
| } |
| } |
| |
| return &taskResultSummary{ |
| unreachable: false, |
| pending: pendingCount, |
| failed: errCount, |
| failedMandatory: errMandatoryCount, |
| passed: passedCount, |
| } |
| } |
| |
| func (b *Cloud) runTasksWithTaskResults(context *IntegrationContext, output IntegrationOutputWriter, fetchTaskStage taskStageReadFunc) error { |
| return context.Poll(func(i int) (bool, error) { |
| stage, err := fetchTaskStage(b, context.StopContext) |
| |
| if err != nil { |
| return false, generalError("Failed to retrieve task stage", err) |
| } |
| |
| summary := summarizeTaskResults(stage.TaskResults) |
| |
| if summary.unreachable { |
| output.Output("Skipping task results.") |
| output.End() |
| return false, nil |
| } |
| |
| if summary.pending > 0 { |
| pendingMessage := "%d tasks still pending, %d passed, %d failed ... " |
| message := fmt.Sprintf(pendingMessage, summary.pending, summary.passed, summary.failed) |
| |
| if i%4 == 0 { |
| if i > 0 { |
| output.OutputElapsed(message, len(pendingMessage)) // Up to 2 digits are allowed by the max message allocation |
| } |
| } |
| return true, nil |
| } |
| |
| // No more tasks pending/running. Print all the results. |
| |
| // Track the first task name that is a mandatory enforcement level breach. |
| var firstMandatoryTaskFailed *string = nil |
| |
| if i == 0 { |
| output.Output(fmt.Sprintf("All tasks completed! %d passed, %d failed", summary.passed, summary.failed)) |
| } else { |
| output.OutputElapsed(fmt.Sprintf("All tasks completed! %d passed, %d failed", summary.passed, summary.failed), 50) |
| } |
| |
| output.Output("") |
| |
| for _, t := range stage.TaskResults { |
| capitalizedStatus := string(t.Status) |
| capitalizedStatus = strings.ToUpper(capitalizedStatus[:1]) + capitalizedStatus[1:] |
| |
| status := "[green]" + capitalizedStatus |
| if t.Status != "passed" { |
| level := string(t.WorkspaceTaskEnforcementLevel) |
| level = strings.ToUpper(level[:1]) + level[1:] |
| status = fmt.Sprintf("[red]%s (%s)", capitalizedStatus, level) |
| |
| if t.WorkspaceTaskEnforcementLevel == "mandatory" && firstMandatoryTaskFailed == nil { |
| firstMandatoryTaskFailed = &t.TaskName |
| } |
| } |
| |
| title := fmt.Sprintf(`%s ⸺ %s`, t.TaskName, status) |
| output.SubOutput(title) |
| |
| if len(t.Message) > 0 { |
| output.SubOutput(fmt.Sprintf("[dim]%s", t.Message)) |
| } |
| if len(t.URL) > 0 { |
| output.SubOutput(fmt.Sprintf("[dim]Details: %s", t.URL)) |
| } |
| output.SubOutput("") |
| } |
| |
| // If a mandatory enforcement level is breached, return an error. |
| var taskErr error = nil |
| var overall string = "[green]Passed" |
| if firstMandatoryTaskFailed != nil { |
| overall = "[red]Failed" |
| if summary.failedMandatory > 1 { |
| taskErr = fmt.Errorf("the run failed because %d mandatory tasks are required to succeed", summary.failedMandatory) |
| } else { |
| taskErr = fmt.Errorf("the run failed because the run task, %s, is required to succeed", *firstMandatoryTaskFailed) |
| } |
| } else if summary.failed > 0 { // we have failures but none of them mandatory |
| overall = "[green]Passed with advisory failures" |
| } |
| |
| output.SubOutput("") |
| output.SubOutput("[bold]Overall Result: " + overall) |
| |
| output.End() |
| |
| return false, taskErr |
| }) |
| } |
| |
| func (b *Cloud) runTasks(ctx *IntegrationContext, output IntegrationOutputWriter, stageID string) error { |
| return b.runTasksWithTaskResults(ctx, output, func(b *Cloud, stopCtx context.Context) (*tfe.TaskStage, error) { |
| options := tfe.TaskStageReadOptions{ |
| Include: []tfe.TaskStageIncludeOpt{tfe.TaskStageTaskResults}, |
| } |
| |
| return b.client.TaskStages.Read(ctx.StopContext, stageID, &options) |
| }) |
| } |