github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/cloud/backend_runTasks.go (about) 1 package cloud 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/hashicorp/go-tfe" 9 ) 10 11 type taskResultSummary struct { 12 unreachable bool 13 pending int 14 failed int 15 failedMandatory int 16 passed int 17 } 18 19 type taskStageReadFunc func(b *Cloud, stopCtx context.Context) (*tfe.TaskStage, error) 20 21 func summarizeTaskResults(taskResults []*tfe.TaskResult) *taskResultSummary { 22 var pendingCount, errCount, errMandatoryCount, passedCount int 23 for _, task := range taskResults { 24 if task.Status == "unreachable" { 25 return &taskResultSummary{ 26 unreachable: true, 27 } 28 } else if task.Status == "running" || task.Status == "pendingCountnding" { 29 pendingCount++ 30 } else if task.Status == "passed" { 31 passedCount++ 32 } else { 33 // Everything else is a failure 34 errCount++ 35 if task.WorkspaceTaskEnforcementLevel == "mandatory" { 36 errMandatoryCount++ 37 } 38 } 39 } 40 41 return &taskResultSummary{ 42 unreachable: false, 43 pending: pendingCount, 44 failed: errCount, 45 failedMandatory: errMandatoryCount, 46 passed: passedCount, 47 } 48 } 49 50 func (b *Cloud) runTasksWithTaskResults(context *IntegrationContext, output IntegrationOutputWriter, fetchTaskStage taskStageReadFunc) error { 51 return context.Poll(func(i int) (bool, error) { 52 stage, err := fetchTaskStage(b, context.StopContext) 53 54 if err != nil { 55 return false, generalError("Failed to retrieve pre-apply task stage", err) 56 } 57 58 summary := summarizeTaskResults(stage.TaskResults) 59 60 if summary.unreachable { 61 output.Output("Skipping task results.") 62 output.End() 63 return false, nil 64 } 65 66 if summary.pending > 0 { 67 pendingMessage := "%d tasks still pending, %d passed, %d failed ... " 68 message := fmt.Sprintf(pendingMessage, summary.pending, summary.passed, summary.failed) 69 70 if i%4 == 0 { 71 if i > 0 { 72 output.OutputElapsed(message, len(pendingMessage)) // Up to 2 digits are allowed by the max message allocation 73 } 74 } 75 return true, nil 76 } 77 78 // No more tasks pending/running. Print all the results. 79 80 // Track the first task name that is a mandatory enforcement level breach. 81 var firstMandatoryTaskFailed *string = nil 82 83 if i == 0 { 84 output.Output(fmt.Sprintf("All tasks completed! %d passed, %d failed", summary.passed, summary.failed)) 85 } else { 86 output.OutputElapsed(fmt.Sprintf("All tasks completed! %d passed, %d failed", summary.passed, summary.failed), 50) 87 } 88 89 output.Output("") 90 91 for _, t := range stage.TaskResults { 92 capitalizedStatus := string(t.Status) 93 capitalizedStatus = strings.ToUpper(capitalizedStatus[:1]) + capitalizedStatus[1:] 94 95 status := "[green]" + capitalizedStatus 96 if t.Status != "passed" { 97 level := string(t.WorkspaceTaskEnforcementLevel) 98 level = strings.ToUpper(level[:1]) + level[1:] 99 status = fmt.Sprintf("[red]%s (%s)", capitalizedStatus, level) 100 101 if t.WorkspaceTaskEnforcementLevel == "mandatory" && firstMandatoryTaskFailed == nil { 102 firstMandatoryTaskFailed = &t.TaskName 103 } 104 } 105 106 title := fmt.Sprintf(`%s ⸺ %s`, t.TaskName, status) 107 output.SubOutput(title) 108 109 output.SubOutput(fmt.Sprintf("[dim]%s", t.Message)) 110 output.SubOutput("") 111 } 112 113 // If a mandatory enforcement level is breached, return an error. 114 var taskErr error = nil 115 var overall string = "[green]Passed" 116 if firstMandatoryTaskFailed != nil { 117 overall = "[red]Failed" 118 if summary.failedMandatory > 1 { 119 taskErr = fmt.Errorf("the run failed because %d mandatory tasks are required to succeed", summary.failedMandatory) 120 } else { 121 taskErr = fmt.Errorf("the run failed because the run task, %s, is required to succeed", *firstMandatoryTaskFailed) 122 } 123 } else if summary.failed > 0 { // we have failures but none of them mandatory 124 overall = "[green]Passed with advisory failures" 125 } 126 127 output.SubOutput("") 128 output.SubOutput("[bold]Overall Result: " + overall) 129 130 output.End() 131 132 return false, taskErr 133 }) 134 } 135 136 func (b *Cloud) runTasks(ctx *IntegrationContext, output IntegrationOutputWriter, stageID string) error { 137 return b.runTasksWithTaskResults(ctx, output, func(b *Cloud, stopCtx context.Context) (*tfe.TaskStage, error) { 138 options := tfe.TaskStageReadOptions{ 139 Include: []tfe.TaskStageIncludeOps{tfe.TaskStageTaskResults}, 140 } 141 142 return b.client.TaskStages.Read(ctx.StopContext, stageID, &options) 143 }) 144 }