2022-03-22 16:13:00 -05:00
|
|
|
package runner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2022-11-16 15:42:57 -06:00
|
|
|
"path"
|
2022-03-22 16:13:00 -05:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/nektos/act/pkg/common"
|
2022-11-16 15:42:57 -06:00
|
|
|
"github.com/nektos/act/pkg/container"
|
2022-05-24 08:36:06 -05:00
|
|
|
"github.com/nektos/act/pkg/exprparser"
|
2022-03-22 16:13:00 -05:00
|
|
|
"github.com/nektos/act/pkg/model"
|
|
|
|
)
|
|
|
|
|
|
|
|
type step interface {
|
|
|
|
pre() common.Executor
|
|
|
|
main() common.Executor
|
|
|
|
post() common.Executor
|
|
|
|
|
|
|
|
getRunContext() *RunContext
|
2022-10-06 16:58:16 -05:00
|
|
|
getGithubContext(ctx context.Context) *model.GithubContext
|
2022-03-22 16:13:00 -05:00
|
|
|
getStepModel() *model.Step
|
|
|
|
getEnv() *map[string]string
|
2022-06-17 10:55:21 -05:00
|
|
|
getIfExpression(context context.Context, stage stepStage) string
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
|
2022-05-24 08:36:06 -05:00
|
|
|
type stepStage int
|
|
|
|
|
|
|
|
const (
|
|
|
|
stepStagePre stepStage = iota
|
|
|
|
stepStageMain
|
|
|
|
stepStagePost
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s stepStage) String() string {
|
|
|
|
switch s {
|
|
|
|
case stepStagePre:
|
|
|
|
return "Pre"
|
|
|
|
case stepStageMain:
|
|
|
|
return "Main"
|
|
|
|
case stepStagePost:
|
|
|
|
return "Post"
|
|
|
|
}
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s stepStage) getStepName(stepModel *model.Step) string {
|
|
|
|
switch s {
|
|
|
|
case stepStagePre:
|
|
|
|
return fmt.Sprintf("pre-%s", stepModel.ID)
|
|
|
|
case stepStageMain:
|
|
|
|
return stepModel.ID
|
|
|
|
case stepStagePost:
|
|
|
|
return fmt.Sprintf("post-%s", stepModel.ID)
|
|
|
|
}
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
|
|
|
|
func runStepExecutor(step step, stage stepStage, executor common.Executor) common.Executor {
|
2022-03-22 16:13:00 -05:00
|
|
|
return func(ctx context.Context) error {
|
2022-06-17 10:55:21 -05:00
|
|
|
logger := common.Logger(ctx)
|
2022-03-22 16:13:00 -05:00
|
|
|
rc := step.getRunContext()
|
|
|
|
stepModel := step.getStepModel()
|
|
|
|
|
2022-06-17 10:55:21 -05:00
|
|
|
ifExpression := step.getIfExpression(ctx, stage)
|
2022-05-24 08:36:06 -05:00
|
|
|
rc.CurrentStep = stage.getStepName(stepModel)
|
|
|
|
|
2022-03-22 16:13:00 -05:00
|
|
|
rc.StepResults[rc.CurrentStep] = &model.StepResult{
|
|
|
|
Outcome: model.StepStatusSuccess,
|
|
|
|
Conclusion: model.StepStatusSuccess,
|
|
|
|
Outputs: make(map[string]string),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := setupEnv(ctx, step)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-05-24 08:36:06 -05:00
|
|
|
runStep, err := isStepEnabled(ctx, ifExpression, step, stage)
|
2022-03-22 16:13:00 -05:00
|
|
|
if err != nil {
|
|
|
|
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusFailure
|
|
|
|
rc.StepResults[rc.CurrentStep].Outcome = model.StepStatusFailure
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !runStep {
|
|
|
|
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusSkipped
|
|
|
|
rc.StepResults[rc.CurrentStep].Outcome = model.StepStatusSkipped
|
2022-06-17 10:55:21 -05:00
|
|
|
logger.WithField("stepResult", rc.StepResults[rc.CurrentStep].Outcome).Debugf("Skipping step '%s' due to '%s'", stepModel, ifExpression)
|
2022-03-22 16:13:00 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-16 15:55:23 -06:00
|
|
|
stepString := rc.ExprEval.Interpolate(ctx, stepModel.String())
|
2022-05-11 14:06:05 -05:00
|
|
|
if strings.Contains(stepString, "::add-mask::") {
|
|
|
|
stepString = "add-mask command"
|
|
|
|
}
|
2022-06-17 10:55:21 -05:00
|
|
|
logger.Infof("\u2B50 Run %s %s", stage, stepString)
|
2022-03-22 16:13:00 -05:00
|
|
|
|
2022-11-16 15:42:57 -06:00
|
|
|
// Prepare and clean Runner File Commands
|
|
|
|
actPath := rc.JobContainer.GetActPath()
|
|
|
|
outputFileCommand := path.Join("workflow", "outputcmd.txt")
|
|
|
|
stateFileCommand := path.Join("workflow", "statecmd.txt")
|
|
|
|
(*step.getEnv())["GITHUB_OUTPUT"] = path.Join(actPath, outputFileCommand)
|
|
|
|
(*step.getEnv())["GITHUB_STATE"] = path.Join(actPath, stateFileCommand)
|
|
|
|
_ = rc.JobContainer.Copy(actPath, &container.FileEntry{
|
|
|
|
Name: outputFileCommand,
|
|
|
|
Mode: 0666,
|
|
|
|
}, &container.FileEntry{
|
|
|
|
Name: stateFileCommand,
|
|
|
|
Mode: 0666,
|
|
|
|
})(ctx)
|
|
|
|
|
2022-03-22 16:13:00 -05:00
|
|
|
err = executor(ctx)
|
|
|
|
|
|
|
|
if err == nil {
|
2022-06-17 10:55:21 -05:00
|
|
|
logger.WithField("stepResult", rc.StepResults[rc.CurrentStep].Outcome).Infof(" \u2705 Success - %s %s", stage, stepString)
|
2022-03-22 16:13:00 -05:00
|
|
|
} else {
|
|
|
|
rc.StepResults[rc.CurrentStep].Outcome = model.StepStatusFailure
|
2022-09-08 09:20:39 -05:00
|
|
|
|
|
|
|
continueOnError, parseErr := isContinueOnError(ctx, stepModel.RawContinueOnError, step, stage)
|
|
|
|
if parseErr != nil {
|
|
|
|
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusFailure
|
|
|
|
return parseErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if continueOnError {
|
2022-06-17 10:55:21 -05:00
|
|
|
logger.Infof("Failed but continue next step")
|
2022-03-22 16:13:00 -05:00
|
|
|
err = nil
|
|
|
|
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusSuccess
|
|
|
|
} else {
|
|
|
|
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusFailure
|
|
|
|
}
|
2022-06-17 10:55:21 -05:00
|
|
|
|
|
|
|
logger.WithField("stepResult", rc.StepResults[rc.CurrentStep].Outcome).Errorf(" \u274C Failure - %s %s", stage, stepString)
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
2022-11-16 15:42:57 -06:00
|
|
|
// Process Runner File Commands
|
|
|
|
orgerr := err
|
|
|
|
state := map[string]string{}
|
|
|
|
err = rc.JobContainer.UpdateFromEnv(path.Join(actPath, stateFileCommand), &state)(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for k, v := range state {
|
|
|
|
rc.saveState(ctx, map[string]string{"name": k}, v)
|
|
|
|
}
|
|
|
|
output := map[string]string{}
|
|
|
|
err = rc.JobContainer.UpdateFromEnv(path.Join(actPath, outputFileCommand), &output)(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for k, v := range output {
|
|
|
|
rc.setOutput(ctx, map[string]string{"name": k}, v)
|
|
|
|
}
|
|
|
|
if orgerr != nil {
|
|
|
|
return orgerr
|
|
|
|
}
|
2022-03-22 16:13:00 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupEnv(ctx context.Context, step step) error {
|
|
|
|
rc := step.getRunContext()
|
|
|
|
|
2022-06-17 10:55:21 -05:00
|
|
|
mergeEnv(ctx, step)
|
2022-03-22 16:13:00 -05:00
|
|
|
err := rc.JobContainer.UpdateFromImageEnv(step.getEnv())(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = rc.JobContainer.UpdateFromEnv((*step.getEnv())["GITHUB_ENV"], step.getEnv())(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = rc.JobContainer.UpdateFromPath(step.getEnv())(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-06 16:58:16 -05:00
|
|
|
// merge step env last, since it should not be overwritten
|
|
|
|
mergeIntoMap(step.getEnv(), step.getStepModel().GetEnv())
|
2022-03-22 16:13:00 -05:00
|
|
|
|
2022-10-06 16:58:16 -05:00
|
|
|
exprEval := rc.NewExpressionEvaluator(ctx)
|
2022-03-22 16:13:00 -05:00
|
|
|
for k, v := range *step.getEnv() {
|
2022-12-06 10:46:20 -06:00
|
|
|
if !strings.HasPrefix(k, "INPUT_") {
|
|
|
|
(*step.getEnv())[k] = exprEval.Interpolate(ctx, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// after we have an evaluated step context, update the expresson evaluator with a new env context
|
|
|
|
// you can use step level env in the with property of a uses construct
|
|
|
|
exprEval = rc.NewExpressionEvaluatorWithEnv(ctx, *step.getEnv())
|
|
|
|
for k, v := range *step.getEnv() {
|
|
|
|
if strings.HasPrefix(k, "INPUT_") {
|
|
|
|
(*step.getEnv())[k] = exprEval.Interpolate(ctx, v)
|
|
|
|
}
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
common.Logger(ctx).Debugf("setupEnv => %v", *step.getEnv())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-17 10:55:21 -05:00
|
|
|
func mergeEnv(ctx context.Context, step step) {
|
2022-03-22 16:13:00 -05:00
|
|
|
env := step.getEnv()
|
|
|
|
rc := step.getRunContext()
|
|
|
|
job := rc.Run.Job()
|
|
|
|
|
|
|
|
c := job.Container()
|
|
|
|
if c != nil {
|
|
|
|
mergeIntoMap(env, rc.GetEnv(), c.Env)
|
|
|
|
} else {
|
|
|
|
mergeIntoMap(env, rc.GetEnv())
|
|
|
|
}
|
|
|
|
|
2022-11-16 15:29:45 -06:00
|
|
|
path := rc.JobContainer.GetPathVariableName()
|
|
|
|
if (*env)[path] == "" {
|
|
|
|
(*env)[path] = rc.JobContainer.DefaultPathVariable()
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
if rc.ExtraPath != nil && len(rc.ExtraPath) > 0 {
|
2022-11-16 15:29:45 -06:00
|
|
|
(*env)[path] = rc.JobContainer.JoinPathVariable(append(rc.ExtraPath, (*env)[path])...)
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
|
2022-10-06 16:58:16 -05:00
|
|
|
rc.withGithubEnv(ctx, step.getGithubContext(ctx), *env)
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
|
2022-05-24 08:36:06 -05:00
|
|
|
func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
|
2022-03-22 16:13:00 -05:00
|
|
|
rc := step.getRunContext()
|
|
|
|
|
2022-05-24 08:36:06 -05:00
|
|
|
var defaultStatusCheck exprparser.DefaultStatusCheck
|
|
|
|
if stage == stepStagePost {
|
|
|
|
defaultStatusCheck = exprparser.DefaultStatusCheckAlways
|
|
|
|
} else {
|
|
|
|
defaultStatusCheck = exprparser.DefaultStatusCheckSuccess
|
|
|
|
}
|
|
|
|
|
2022-06-17 10:55:21 -05:00
|
|
|
runStep, err := EvalBool(ctx, rc.NewStepExpressionEvaluator(ctx, step), expr, defaultStatusCheck)
|
2022-03-22 16:13:00 -05:00
|
|
|
if err != nil {
|
2022-05-24 08:36:06 -05:00
|
|
|
return false, fmt.Errorf(" \u274C Error in if-expression: \"if: %s\" (%s)", expr, err)
|
2022-03-22 16:13:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return runStep, nil
|
|
|
|
}
|
|
|
|
|
2022-09-08 09:20:39 -05:00
|
|
|
func isContinueOnError(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
|
|
|
|
// https://github.com/github/docs/blob/3ae84420bd10997bb5f35f629ebb7160fe776eae/content/actions/reference/workflow-syntax-for-github-actions.md?plain=true#L962
|
|
|
|
if len(strings.TrimSpace(expr)) == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := step.getRunContext()
|
|
|
|
|
|
|
|
continueOnError, err := EvalBool(ctx, rc.NewStepExpressionEvaluator(ctx, step), expr, exprparser.DefaultStatusCheckNone)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf(" \u274C Error in continue-on-error-expression: \"continue-on-error: %s\" (%s)", expr, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return continueOnError, nil
|
|
|
|
}
|
|
|
|
|
2022-03-22 16:13:00 -05:00
|
|
|
func mergeIntoMap(target *map[string]string, maps ...map[string]string) {
|
|
|
|
for _, m := range maps {
|
|
|
|
for k, v := range m {
|
|
|
|
(*target)[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|