github.com/nektos/act@v0.2.63/pkg/runner/job_executor.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/nektos/act/pkg/common"
     9  	"github.com/nektos/act/pkg/model"
    10  )
    11  
    12  type jobInfo interface {
    13  	matrix() map[string]interface{}
    14  	steps() []*model.Step
    15  	startContainer() common.Executor
    16  	stopContainer() common.Executor
    17  	closeContainer() common.Executor
    18  	interpolateOutputs() common.Executor
    19  	result(result string)
    20  }
    21  
    22  //nolint:contextcheck,gocyclo
    23  func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor {
    24  	steps := make([]common.Executor, 0)
    25  	preSteps := make([]common.Executor, 0)
    26  	var postExecutor common.Executor
    27  
    28  	steps = append(steps, func(ctx context.Context) error {
    29  		logger := common.Logger(ctx)
    30  		if len(info.matrix()) > 0 {
    31  			logger.Infof("\U0001F9EA  Matrix: %v", info.matrix())
    32  		}
    33  		return nil
    34  	})
    35  
    36  	infoSteps := info.steps()
    37  
    38  	if len(infoSteps) == 0 {
    39  		return common.NewDebugExecutor("No steps found")
    40  	}
    41  
    42  	preSteps = append(preSteps, func(ctx context.Context) error {
    43  		// Have to be skipped for some Tests
    44  		if rc.Run == nil {
    45  			return nil
    46  		}
    47  		rc.ExprEval = rc.NewExpressionEvaluator(ctx)
    48  		// evaluate environment variables since they can contain
    49  		// GitHub's special environment variables.
    50  		for k, v := range rc.GetEnv() {
    51  			rc.Env[k] = rc.ExprEval.Interpolate(ctx, v)
    52  		}
    53  		return nil
    54  	})
    55  
    56  	for i, stepModel := range infoSteps {
    57  		stepModel := stepModel
    58  		if stepModel == nil {
    59  			return func(ctx context.Context) error {
    60  				return fmt.Errorf("invalid Step %v: missing run or uses key", i)
    61  			}
    62  		}
    63  		if stepModel.ID == "" {
    64  			stepModel.ID = fmt.Sprintf("%d", i)
    65  		}
    66  
    67  		step, err := sf.newStep(stepModel, rc)
    68  
    69  		if err != nil {
    70  			return common.NewErrorExecutor(err)
    71  		}
    72  
    73  		preSteps = append(preSteps, useStepLogger(rc, stepModel, stepStagePre, step.pre()))
    74  
    75  		stepExec := step.main()
    76  		steps = append(steps, useStepLogger(rc, stepModel, stepStageMain, func(ctx context.Context) error {
    77  			logger := common.Logger(ctx)
    78  			err := stepExec(ctx)
    79  			if err != nil {
    80  				logger.Errorf("%v", err)
    81  				common.SetJobError(ctx, err)
    82  			} else if ctx.Err() != nil {
    83  				logger.Errorf("%v", ctx.Err())
    84  				common.SetJobError(ctx, ctx.Err())
    85  			}
    86  			return nil
    87  		}))
    88  
    89  		postExec := useStepLogger(rc, stepModel, stepStagePost, step.post())
    90  		if postExecutor != nil {
    91  			// run the post executor in reverse order
    92  			postExecutor = postExec.Finally(postExecutor)
    93  		} else {
    94  			postExecutor = postExec
    95  		}
    96  	}
    97  
    98  	postExecutor = postExecutor.Finally(func(ctx context.Context) error {
    99  		jobError := common.JobError(ctx)
   100  		var err error
   101  		if rc.Config.AutoRemove || jobError == nil {
   102  			// always allow 1 min for stopping and removing the runner, even if we were cancelled
   103  			ctx, cancel := context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), time.Minute)
   104  			defer cancel()
   105  
   106  			logger := common.Logger(ctx)
   107  			logger.Infof("Cleaning up container for job %s", rc.JobName)
   108  			if err = info.stopContainer()(ctx); err != nil {
   109  				logger.Errorf("Error while stop job container: %v", err)
   110  			}
   111  		}
   112  		setJobResult(ctx, info, rc, jobError == nil)
   113  		setJobOutputs(ctx, rc)
   114  
   115  		return err
   116  	})
   117  
   118  	pipeline := make([]common.Executor, 0)
   119  	pipeline = append(pipeline, preSteps...)
   120  	pipeline = append(pipeline, steps...)
   121  
   122  	return common.NewPipelineExecutor(info.startContainer(), common.NewPipelineExecutor(pipeline...).
   123  		Finally(func(ctx context.Context) error { //nolint:contextcheck
   124  			var cancel context.CancelFunc
   125  			if ctx.Err() == context.Canceled {
   126  				// in case of an aborted run, we still should execute the
   127  				// post steps to allow cleanup.
   128  				ctx, cancel = context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), 5*time.Minute)
   129  				defer cancel()
   130  			}
   131  			return postExecutor(ctx)
   132  		}).
   133  		Finally(info.interpolateOutputs()).
   134  		Finally(info.closeContainer()))
   135  }
   136  
   137  func setJobResult(ctx context.Context, info jobInfo, rc *RunContext, success bool) {
   138  	logger := common.Logger(ctx)
   139  
   140  	jobResult := "success"
   141  	// we have only one result for a whole matrix build, so we need
   142  	// to keep an existing result state if we run a matrix
   143  	if len(info.matrix()) > 0 && rc.Run.Job().Result != "" {
   144  		jobResult = rc.Run.Job().Result
   145  	}
   146  
   147  	if !success {
   148  		jobResult = "failure"
   149  	}
   150  
   151  	info.result(jobResult)
   152  	if rc.caller != nil {
   153  		// set reusable workflow job result
   154  		rc.caller.runContext.result(jobResult)
   155  	}
   156  
   157  	jobResultMessage := "succeeded"
   158  	if jobResult != "success" {
   159  		jobResultMessage = "failed"
   160  	}
   161  
   162  	logger.WithField("jobResult", jobResult).Infof("\U0001F3C1  Job %s", jobResultMessage)
   163  }
   164  
   165  func setJobOutputs(ctx context.Context, rc *RunContext) {
   166  	if rc.caller != nil {
   167  		// map outputs for reusable workflows
   168  		callerOutputs := make(map[string]string)
   169  
   170  		ee := rc.NewExpressionEvaluator(ctx)
   171  
   172  		for k, v := range rc.Run.Workflow.WorkflowCallConfig().Outputs {
   173  			callerOutputs[k] = ee.Interpolate(ctx, ee.Interpolate(ctx, v.Value))
   174  		}
   175  
   176  		rc.caller.runContext.Run.Job().Outputs = callerOutputs
   177  	}
   178  }
   179  
   180  func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor {
   181  	return func(ctx context.Context) error {
   182  		ctx = withStepLogger(ctx, stepModel.ID, rc.ExprEval.Interpolate(ctx, stepModel.String()), stage.String())
   183  
   184  		rawLogger := common.Logger(ctx).WithField("raw_output", true)
   185  		logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
   186  			if rc.Config.LogOutput {
   187  				rawLogger.Infof("%s", s)
   188  			} else {
   189  				rawLogger.Debugf("%s", s)
   190  			}
   191  			return true
   192  		})
   193  
   194  		oldout, olderr := rc.JobContainer.ReplaceLogWriter(logWriter, logWriter)
   195  		defer rc.JobContainer.ReplaceLogWriter(oldout, olderr)
   196  
   197  		return executor(ctx)
   198  	}
   199  }