140 lines
4.0 KiB
Go
140 lines
4.0 KiB
Go
|
package cloud
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/hashicorp/go-tfe"
|
||
|
)
|
||
|
|
||
|
type taskResultSummary struct {
|
||
|
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 pe, er, erm, pa int
|
||
|
for _, task := range taskResults {
|
||
|
if task.Status == "running" || task.Status == "pending" {
|
||
|
pe++
|
||
|
} else if task.Status == "passed" {
|
||
|
pa++
|
||
|
} else {
|
||
|
// Everything else is a failure
|
||
|
er++
|
||
|
if task.WorkspaceTaskEnforcementLevel == "mandatory" {
|
||
|
erm++
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return taskResultSummary{
|
||
|
pending: pe,
|
||
|
failed: er,
|
||
|
failedMandatory: erm,
|
||
|
passed: pa,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// elapsedMessageMax is 50 chars: the length of this message with 6 digits
|
||
|
// 99 tasks still pending, 99 passed, 99 failed ...
|
||
|
const elapsedMessageMax int = 50
|
||
|
|
||
|
func (b *Cloud) runTasksWithTaskResults(context *IntegrationContext, output IntegrationOutputWriter, fetchTaskStage taskStageReadFunc) error {
|
||
|
// TODO: Do not fetch run tasks if there are no changes to infrastructure
|
||
|
// if !context.Run.HasChanges {
|
||
|
// output.Output("No Changes. Tasks will not run")
|
||
|
// output.End()
|
||
|
|
||
|
// return nil
|
||
|
// }
|
||
|
|
||
|
return context.Poll(func(i int) (bool, error) {
|
||
|
// TODO: get the stage that corresponds to an argument passed to this function
|
||
|
stage, err := fetchTaskStage(b, context.StopContext)
|
||
|
|
||
|
if err != nil {
|
||
|
return false, generalError("Failed to retrieve pre-apply task stage", err)
|
||
|
}
|
||
|
|
||
|
summary := summarizeTaskResults(stage.TaskResults)
|
||
|
if summary.pending > 0 {
|
||
|
message := fmt.Sprintf("%d tasks still pending, %d passed, %d failed ... ", summary.pending, summary.passed, summary.failed)
|
||
|
|
||
|
if i%4 == 0 {
|
||
|
if i > 0 {
|
||
|
output.OutputElapsed(message, elapsedMessageMax)
|
||
|
}
|
||
|
}
|
||
|
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)
|
||
|
|
||
|
output.SubOutput(fmt.Sprintf("[dim]%s", t.Message))
|
||
|
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"
|
||
|
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: "task_results",
|
||
|
}
|
||
|
|
||
|
return b.client.TaskStages.Read(ctx.StopContext, stageID, &options)
|
||
|
})
|
||
|
}
|