mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-10-28 19:06:02 +08:00
fix: restore the caller's event and inputs in reusable workflow jobs (#1250)
Fixes https://github.com/go-gitea/gitea/issues/38705 Gitea side: https://github.com/go-gitea/gitea/pull/39452 Jobs of a called workflow currently see `gitea.event_name == "workflow_call"`, and their `gitea.event.inputs` is replaced by the caller's `with:`. On GitHub they keep the caller's event, and their `inputs` are the run's `workflow_dispatch` inputs overlaid with the caller's `with:`. For compatibility with older runners, Gitea keeps sending that form and adds a `gitea_workflow_call` key with the original trigger event name and inputs of the run, and the `inputs` Gitea resolved for the job. When the key is present, the runner restores `gitea.event_name` and `gitea.event.inputs` from it, and uses the resolved `inputs` for the job's expressions and the setup log. Without that key (older Gitea, or jobs outside called workflows), nothing changes. Reviewed-on: https://gitea.com/gitea/runner/pulls/1250 Reviewed-by: bircni <bircni@icloud.com> Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
@@ -248,6 +248,14 @@ func inputsFromEnv(env map[string]string) map[string]any {
|
||||
func getEvaluatorInputs(rc *RunContext, stepInputs map[string]any, ghc *model.GithubContext) map[string]any {
|
||||
inputs := map[string]any{}
|
||||
|
||||
// only a called workflow's job itself takes the inputs Gitea resolved,
|
||||
// composite actions copy its Config but have their own `inputs`
|
||||
if rc.Config.WorkflowCallInputs != nil && rc.Parent == nil {
|
||||
maps.Copy(inputs, rc.Config.WorkflowCallInputs)
|
||||
maps.Copy(inputs, stepInputs)
|
||||
return inputs
|
||||
}
|
||||
|
||||
maps.Copy(inputs, rc.workflowCallInputs)
|
||||
maps.Copy(inputs, stepInputs)
|
||||
|
||||
|
||||
@@ -392,6 +392,28 @@ jobs:
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetEvaluatorInputsResolvedByGitea(t *testing.T) {
|
||||
wf, err := model.ReadWorkflow(strings.NewReader(`
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
name: {type: string, default: callee}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
name: {type: string}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
config := &Config{Workdir: ".", WorkflowCallInputs: map[string]any{"name": "caller", "target": "dispatch"}}
|
||||
ghc := &model.GithubContext{EventName: "workflow_dispatch", Event: map[string]any{"inputs": map[string]any{"name": "dispatch"}}}
|
||||
|
||||
// the event's own inputs must not override those Gitea resolved
|
||||
job := &RunContext{Config: config, Run: &model.Run{JobID: "job1", Workflow: wf}}
|
||||
assert.Equal(t, map[string]any{"name": "caller", "target": "dispatch"}, getEvaluatorInputs(job, nil, ghc))
|
||||
|
||||
composite := &RunContext{Config: config, Run: &model.Run{JobID: "job1", Workflow: &model.Workflow{}}, Parent: job}
|
||||
assert.Equal(t, map[string]any{"arg": "x"}, getEvaluatorInputs(composite, map[string]any{"arg": "x"}, ghc))
|
||||
}
|
||||
|
||||
func TestResolveWorkflowCallToleratesMismatchesAndPassesInheritedSecretsVerbatim(t *testing.T) {
|
||||
callerWorkflow, err := model.ReadWorkflow(strings.NewReader(`
|
||||
jobs:
|
||||
|
||||
@@ -62,6 +62,7 @@ type Config struct {
|
||||
NoActionPatch bool // run actions exactly as published, applying no compatibility patches, see patch_actions.go
|
||||
|
||||
PresetGitHubContext *model.GithubContext // overrides actor, ref, repository, token and related context fields
|
||||
WorkflowCallInputs map[string]any // the `inputs` Gitea resolved for a called workflow's job, nil for other jobs
|
||||
EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath
|
||||
ContainerNamePrefix string // the prefix of container name
|
||||
ContainerMaxLifetime time.Duration // the max lifetime of job containers
|
||||
|
||||
@@ -350,6 +350,25 @@ func (r *Runner) cloneEnvs() map[string]string {
|
||||
return envs
|
||||
}
|
||||
|
||||
// restoreCalledWorkflowContext undoes Gitea's rewrite of `event_name` and `event.inputs` for older runners,
|
||||
// and returns the job's `inputs` from `gitea_workflow_call`, nil if it is not a called workflow's job.
|
||||
// See https://github.com/go-gitea/gitea/pull/39452
|
||||
func restoreCalledWorkflowContext(task *runnerv1.Task) map[string]any {
|
||||
workflowCall := task.GetContext().GetFields()["gitea_workflow_call"].GetStructValue().GetFields()
|
||||
if workflowCall == nil {
|
||||
return nil
|
||||
}
|
||||
task.Context.Fields["event_name"] = workflowCall["original_event_name"]
|
||||
if event := task.Context.Fields["event"].GetStructValue(); event != nil {
|
||||
if inputs, ok := workflowCall["original_event_inputs"]; ok {
|
||||
event.Fields["inputs"] = inputs
|
||||
} else {
|
||||
delete(event.Fields, "inputs")
|
||||
}
|
||||
}
|
||||
return workflowCall["inputs"].GetStructValue().AsMap()
|
||||
}
|
||||
|
||||
// getDefaultActionsURL
|
||||
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
|
||||
// it should be set to GithubMirror first.
|
||||
@@ -398,6 +417,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
}
|
||||
}()
|
||||
|
||||
workflowCallInputs := restoreCalledWorkflowContext(task)
|
||||
r.reportSetup(reporter, task)
|
||||
|
||||
workflow, jobID, err := generateWorkflow(task)
|
||||
@@ -536,6 +556,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
NoSkipCheckout: true,
|
||||
DisableActEnv: r.cfg.Runner.SetActEnv != nil && !*r.cfg.Runner.SetActEnv,
|
||||
PresetGitHubContext: preset,
|
||||
WorkflowCallInputs: workflowCallInputs,
|
||||
EventJSON: string(eventJSON),
|
||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||
ContainerMaxLifetime: maxLifetime,
|
||||
|
||||
@@ -358,3 +358,48 @@ func TestNewRunnerCacheServiceV2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreCalledWorkflowContext(t *testing.T) {
|
||||
newTask := func(workflowCall map[string]any) *runnerv1.Task {
|
||||
values := map[string]any{
|
||||
"event_name": "workflow_call",
|
||||
"event": map[string]any{"ref": "refs/heads/main", "inputs": map[string]any{"env": "leaf"}},
|
||||
}
|
||||
if workflowCall != nil {
|
||||
values["gitea_workflow_call"] = workflowCall
|
||||
}
|
||||
taskCtx, err := structpb.NewStruct(values)
|
||||
require.NoError(t, err)
|
||||
return &runnerv1.Task{Context: taskCtx}
|
||||
}
|
||||
|
||||
t.Run("dispatched run", func(t *testing.T) {
|
||||
task := newTask(map[string]any{
|
||||
"original_event_name": "workflow_dispatch",
|
||||
"original_event_inputs": map[string]any{"target": "prod"},
|
||||
"inputs": map[string]any{"target": "prod", "env": "leaf"},
|
||||
})
|
||||
assert.Equal(t, map[string]any{"target": "prod", "env": "leaf"}, restoreCalledWorkflowContext(task))
|
||||
taskCtx := task.Context.AsMap()
|
||||
assert.Equal(t, "workflow_dispatch", taskCtx["event_name"])
|
||||
assert.Equal(t, map[string]any{"ref": "refs/heads/main", "inputs": map[string]any{"target": "prod"}}, taskCtx["event"])
|
||||
assert.Equal(t, []string{"::group::Inputs", "env: leaf", "target: prod", "::endgroup::"}, inputLines(task.Context.Fields))
|
||||
})
|
||||
|
||||
t.Run("event without inputs", func(t *testing.T) {
|
||||
task := newTask(map[string]any{
|
||||
"original_event_name": "push",
|
||||
"inputs": map[string]any{"env": "leaf"},
|
||||
})
|
||||
assert.Equal(t, map[string]any{"env": "leaf"}, restoreCalledWorkflowContext(task))
|
||||
taskCtx := task.Context.AsMap()
|
||||
assert.Equal(t, "push", taskCtx["event_name"])
|
||||
assert.Equal(t, map[string]any{"ref": "refs/heads/main"}, taskCtx["event"])
|
||||
})
|
||||
|
||||
t.Run("not a called workflow's job", func(t *testing.T) {
|
||||
task := newTask(nil)
|
||||
assert.Nil(t, restoreCalledWorkflowContext(task))
|
||||
assert.Equal(t, "workflow_call", task.Context.AsMap()["event_name"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,10 +56,15 @@ func (r *Runner) setupLines(task *runnerv1.Task) []string {
|
||||
return append(lines, "::endgroup::")
|
||||
}
|
||||
|
||||
// inputLines lists the inputs the run was triggered with, as workflow_dispatch and workflow_call
|
||||
// carry them in the event payload. Empty when the event has none.
|
||||
// inputLines lists the inputs of the job: those Gitea resolved for a called workflow's job,
|
||||
// or the ones a workflow_dispatch event carries in its payload. Empty when there are none.
|
||||
func inputLines(fields map[string]*structpb.Value) []string {
|
||||
inputs := fields["event"].GetStructValue().GetFields()["inputs"].GetStructValue().GetFields()
|
||||
// For a called workflow's job, restore its inputs from `gitea_workflow_call`.
|
||||
// See https://github.com/go-gitea/gitea/pull/39452.
|
||||
if workflowCall := fields["gitea_workflow_call"].GetStructValue(); workflowCall != nil {
|
||||
inputs = workflowCall.GetFields()["inputs"].GetStructValue().GetFields()
|
||||
}
|
||||
if len(inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user