github.com/opentofu/opentofu@v1.7.1/internal/cloud/backend_taskStage_taskResults.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package cloud 7 8 import ( 9 "fmt" 10 "strings" 11 12 "github.com/hashicorp/go-tfe" 13 ) 14 15 type taskResultSummary struct { 16 unreachable bool 17 pending int 18 failed int 19 failedMandatory int 20 passed int 21 } 22 23 type taskResultSummarizer struct { 24 finished bool 25 cloud *Cloud 26 counter int 27 } 28 29 func newTaskResultSummarizer(b *Cloud, ts *tfe.TaskStage) taskStageSummarizer { 30 if len(ts.TaskResults) == 0 { 31 return nil 32 } 33 return &taskResultSummarizer{ 34 finished: false, 35 cloud: b, 36 } 37 } 38 39 func (trs *taskResultSummarizer) Summarize(context *IntegrationContext, output IntegrationOutputWriter, ts *tfe.TaskStage) (bool, *string, error) { 40 if trs.finished { 41 return false, nil, nil 42 } 43 trs.counter++ 44 45 counts := summarizeTaskResults(ts.TaskResults) 46 47 if counts.pending != 0 { 48 pendingMessage := "%d tasks still pending, %d passed, %d failed ... " 49 message := fmt.Sprintf(pendingMessage, counts.pending, counts.passed, counts.failed) 50 return true, &message, nil 51 } 52 if counts.unreachable { 53 output.Output("Skipping task results.") 54 output.End() 55 return false, nil, nil 56 } 57 58 // Print out the summary 59 trs.runTasksWithTaskResults(output, ts.TaskResults, counts) 60 61 // Mark as finished 62 trs.finished = true 63 64 return false, nil, nil 65 } 66 67 func summarizeTaskResults(taskResults []*tfe.TaskResult) *taskResultSummary { 68 var pendingCount, errCount, errMandatoryCount, passedCount int 69 for _, task := range taskResults { 70 if task.Status == tfe.TaskUnreachable { 71 return &taskResultSummary{ 72 unreachable: true, 73 } 74 } else if task.Status == tfe.TaskRunning || task.Status == tfe.TaskPending { 75 pendingCount++ 76 } else if task.Status == tfe.TaskPassed { 77 passedCount++ 78 } else { 79 // Everything else is a failure 80 errCount++ 81 if task.WorkspaceTaskEnforcementLevel == tfe.Mandatory { 82 errMandatoryCount++ 83 } 84 } 85 } 86 87 return &taskResultSummary{ 88 unreachable: false, 89 pending: pendingCount, 90 failed: errCount, 91 failedMandatory: errMandatoryCount, 92 passed: passedCount, 93 } 94 } 95 96 func (trs *taskResultSummarizer) runTasksWithTaskResults(output IntegrationOutputWriter, taskResults []*tfe.TaskResult, count *taskResultSummary) { 97 // Track the first task name that is a mandatory enforcement level breach. 98 var firstMandatoryTaskFailed *string = nil 99 100 if trs.counter == 0 { 101 output.Output(fmt.Sprintf("All tasks completed! %d passed, %d failed", count.passed, count.failed)) 102 } else { 103 output.OutputElapsed(fmt.Sprintf("All tasks completed! %d passed, %d failed", count.passed, count.failed), 50) 104 } 105 106 output.Output("") 107 108 for _, t := range taskResults { 109 capitalizedStatus := string(t.Status) 110 capitalizedStatus = strings.ToUpper(capitalizedStatus[:1]) + capitalizedStatus[1:] 111 112 status := "[green]" + capitalizedStatus 113 if t.Status != "passed" { 114 level := string(t.WorkspaceTaskEnforcementLevel) 115 level = strings.ToUpper(level[:1]) + level[1:] 116 status = fmt.Sprintf("[red]%s (%s)", capitalizedStatus, level) 117 118 if t.WorkspaceTaskEnforcementLevel == "mandatory" && firstMandatoryTaskFailed == nil { 119 firstMandatoryTaskFailed = &t.TaskName 120 } 121 } 122 123 title := fmt.Sprintf(`%s ⸺ %s`, t.TaskName, status) 124 output.SubOutput(title) 125 126 if len(t.Message) > 0 { 127 output.SubOutput(fmt.Sprintf("[dim]%s", t.Message)) 128 } 129 if len(t.URL) > 0 { 130 output.SubOutput(fmt.Sprintf("[dim]Details: %s", t.URL)) 131 } 132 output.SubOutput("") 133 } 134 135 // If a mandatory enforcement level is breached, return an error. 136 var overall string = "[green]Passed" 137 if firstMandatoryTaskFailed != nil { 138 overall = "[red]Failed" 139 if count.failedMandatory > 1 { 140 output.Output(fmt.Sprintf("[reset][bold][red]Error:[reset][bold]the run failed because %d mandatory tasks are required to succeed", count.failedMandatory)) 141 } else { 142 output.Output(fmt.Sprintf("[reset][bold][red]Error: [reset][bold]the run failed because the run task, %s, is required to succeed", *firstMandatoryTaskFailed)) 143 } 144 } else if count.failed > 0 { // we have failures but none of them mandatory 145 overall = "[green]Passed with advisory failures" 146 } 147 148 output.SubOutput("") 149 output.SubOutput("[bold]Overall Result: " + overall) 150 151 output.End() 152 }