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  }