mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Prevously the cloud backend would only render post-plan run tasks. Now that pre-plan tasks are in beta, this commit updates the plan phase to render pre-plan run tasks. This commit also moves some common code to the common backend as it will be used by other task stages in the future.
150 lines
4.3 KiB
Go
150 lines
4.3 KiB
Go
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)
|
|
})
|
|
}
|