github.com/kevinklinger/open_terraform@v1.3.6/noninternal/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 == "pending" {
    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 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  			if len(t.Message) > 0 {
   110  				output.SubOutput(fmt.Sprintf("[dim]%s", t.Message))
   111  			}
   112  			if len(t.URL) > 0 {
   113  				output.SubOutput(fmt.Sprintf("[dim]Details: %s", t.URL))
   114  			}
   115  			output.SubOutput("")
   116  		}
   117  
   118  		// If a mandatory enforcement level is breached, return an error.
   119  		var taskErr error = nil
   120  		var overall string = "[green]Passed"
   121  		if firstMandatoryTaskFailed != nil {
   122  			overall = "[red]Failed"
   123  			if summary.failedMandatory > 1 {
   124  				taskErr = fmt.Errorf("the run failed because %d mandatory tasks are required to succeed", summary.failedMandatory)
   125  			} else {
   126  				taskErr = fmt.Errorf("the run failed because the run task, %s, is required to succeed", *firstMandatoryTaskFailed)
   127  			}
   128  		} else if summary.failed > 0 { // we have failures but none of them mandatory
   129  			overall = "[green]Passed with advisory failures"
   130  		}
   131  
   132  		output.SubOutput("")
   133  		output.SubOutput("[bold]Overall Result: " + overall)
   134  
   135  		output.End()
   136  
   137  		return false, taskErr
   138  	})
   139  }
   140  
   141  func (b *Cloud) runTasks(ctx *IntegrationContext, output IntegrationOutputWriter, stageID string) error {
   142  	return b.runTasksWithTaskResults(ctx, output, func(b *Cloud, stopCtx context.Context) (*tfe.TaskStage, error) {
   143  		options := tfe.TaskStageReadOptions{
   144  			Include: []tfe.TaskStageIncludeOpt{tfe.TaskStageTaskResults},
   145  		}
   146  
   147  		return b.client.TaskStages.Read(ctx.StopContext, stageID, &options)
   148  	})
   149  }