mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
fix: fail the step or job whose expression cannot be interpolated (#1199)
An expression that failed to evaluate was logged and replaced by an empty string, so a `run:` step executed an empty script and reported success. The same swallow covered `shell:`, `working-directory:`, step `env:`, `with:`, `uses:`, the job's `env:`, `container:`, `services:`, `runs-on:` and outputs, and a called workflow's `with:` and `secrets:`. Every interpolation now propagates its error as actions/runner does: step-level values fail the step, job-level values fail the job at setup, `timeout-minutes` logs the error and runs unbounded, and a job or step name keeps its source text. `defaults.run` and a called workflow's inputs and secrets are resolved once at job setup with the job context rather than per step, and the job's image is resolved once, so host mode and `ImageOS` derive from the image the job started with. Closes https://gitea.com/gitea/runner/issues/392 Closes https://gitea.com/gitea/runner/issues/555 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1199 Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com> Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
committed by
silverwind
co-authored by
silverwind
silverwind
parent
7f53eca716
commit
1745c7c841
+52
-24
@@ -18,6 +18,7 @@ import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
@@ -154,11 +155,9 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
if rc.Run == nil {
|
||||
return nil
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
// evaluate environment variables since they can contain
|
||||
// GitHub's special environment variables.
|
||||
for k, v := range rc.GetEnv() {
|
||||
rc.Env[k] = rc.ExprEval.Interpolate(ctx, v)
|
||||
if err := evaluateJobEnvAndDefaults(ctx, rc); err != nil {
|
||||
reportStepError(ctx, rc, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -240,6 +239,8 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
// swallowed: a bad output fails this job, it must not abandon the rest of the plan
|
||||
if err := info.interpolateOutputs()(ctx); err != nil {
|
||||
reportStepError(ctx, rc, err)
|
||||
} else if err := setJobOutputs(ctx, rc); err != nil {
|
||||
reportStepError(ctx, rc, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -258,7 +259,6 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
logger.Errorf("##[error]%s", EscapeCommandData("Error while stop job container: "+err.Error()))
|
||||
}
|
||||
setJobResult(ctx, info, rc, jobError == nil)
|
||||
setJobOutputs(ctx, rc)
|
||||
|
||||
return err
|
||||
})
|
||||
@@ -413,30 +413,58 @@ func setJobResult(ctx context.Context, info jobInfo, rc *RunContext, success boo
|
||||
logger.WithField("jobResult", jobResult).Infof("Job %s", jobResultMessage)
|
||||
}
|
||||
|
||||
func setJobOutputs(ctx context.Context, rc *RunContext) {
|
||||
if rc.caller != nil {
|
||||
// map outputs for reusable workflows
|
||||
callerOutputs := make(map[string]string)
|
||||
|
||||
ee := rc.NewExpressionEvaluator(ctx)
|
||||
|
||||
for k, v := range rc.Run.Workflow.WorkflowCallConfig().Outputs {
|
||||
callerOutputs[k] = ee.Interpolate(ctx, ee.Interpolate(ctx, v.Value))
|
||||
// evaluateJobEnvAndDefaults resolves the job's env and defaults.run once, as GitHub does at job setup.
|
||||
func evaluateJobEnvAndDefaults(ctx context.Context, rc *RunContext) error {
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
var err error
|
||||
for k, v := range rc.GetEnv() {
|
||||
if rc.Env[k], err = rc.ExprEval.Interpolate(ctx, v); err != nil {
|
||||
return fmt.Errorf("unable to interpolate env %s: %w", k, err)
|
||||
}
|
||||
|
||||
// Matrix combinations of a reusable-workflow caller share the caller's *model.Job;
|
||||
// serialize the write so parallel combos don't race on its Outputs field.
|
||||
callerJob := rc.caller.runContext.Run.Job()
|
||||
defer lockJob(callerJob)()
|
||||
callerJob.Outputs = callerOutputs
|
||||
}
|
||||
defaults := rc.Run.Job().Defaults.Run
|
||||
if rc.jobRunDefaults.Shell, err = rc.ExprEval.Interpolate(ctx, defaults.Shell); err != nil {
|
||||
return fmt.Errorf("unable to interpolate defaults.run.shell: %w", err)
|
||||
}
|
||||
if rc.jobRunDefaults.WorkingDirectory, err = rc.ExprEval.Interpolate(ctx, defaults.WorkingDirectory); err != nil {
|
||||
return fmt.Errorf("unable to interpolate defaults.run.working-directory: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setJobOutputs(ctx context.Context, rc *RunContext) error {
|
||||
if rc.caller == nil {
|
||||
return nil
|
||||
}
|
||||
outputs := rc.Run.Workflow.WorkflowCallConfig().Outputs
|
||||
callerOutputs := make(map[string]string, len(outputs))
|
||||
ee := sync.OnceValue(func() *expressionEvaluator { return rc.NewExpressionEvaluator(ctx) })
|
||||
for k, v := range outputs {
|
||||
value := v.Value
|
||||
for range 2 { // two passes, the value resolves through a job output
|
||||
var err error
|
||||
if value, err = ee().Interpolate(ctx, value); err != nil {
|
||||
return fmt.Errorf("unable to interpolate workflow output %s: %w", k, err)
|
||||
}
|
||||
}
|
||||
callerOutputs[k] = value
|
||||
}
|
||||
|
||||
// Matrix combinations of a reusable-workflow caller share the caller's *model.Job;
|
||||
// serialize the write so parallel combos don't race on its Outputs field.
|
||||
callerJob := rc.caller.runContext.Run.Job()
|
||||
defer lockJob(callerJob)()
|
||||
callerJob.Outputs = callerOutputs
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyJobTimeout applies the job-level timeout-minutes to ctx, mirroring the
|
||||
// step-level evaluateStepTimeout in step.go.
|
||||
func applyJobTimeout(ctx context.Context, rc *RunContext, job *model.Job) (context.Context, context.CancelFunc) {
|
||||
timeout := rc.ExprEval.Interpolate(ctx, job.TimeoutMinutes)
|
||||
if timeout != "" {
|
||||
timeout, err := rc.ExprEval.Interpolate(ctx, job.TimeoutMinutes)
|
||||
if err != nil {
|
||||
common.Logger(ctx).Errorf("An error occurred when attempting to determine the job timeout: %s", err)
|
||||
} else if timeout != "" {
|
||||
if timeoutMinutes, err := strconv.ParseInt(timeout, 10, 64); err == nil {
|
||||
return context.WithTimeout(ctx, time.Duration(timeoutMinutes)*time.Minute)
|
||||
}
|
||||
@@ -635,7 +663,7 @@ func archiveEntryMatchesPath(entryName, requestedPath string) bool {
|
||||
|
||||
func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
ctx = withStepLogger(ctx, stepModel.Number, stepModel.ID, rc.ExprEval.Interpolate(ctx, stepModel.String()), stage.String())
|
||||
ctx = withStepLogger(ctx, stepModel.Number, stepModel.ID, rc.ExprEval.InterpolateName(ctx, stepModel.String()), stage.String())
|
||||
|
||||
logWriter := rc.commandLogWriter(ctx)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user