mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-07 15:50:28 +00: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 <[email protected]> Reviewed-by: silverwind <[email protected]> Co-authored-by: Zettat123 <[email protected]>
This commit is contained in:
1 parent
81add27459
commit
990ebaa2ac
6 files changed
+104
-2
No files matched your search
@@ -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 {
|
func getEvaluatorInputs(rc *RunContext, stepInputs map[string]any, ghc *model.GithubContext) map[string]any {
|
||||||
inputs := 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, rc.workflowCallInputs)
|
||||||
maps.Copy(inputs, stepInputs)
|
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) {
|
func TestResolveWorkflowCallToleratesMismatchesAndPassesInheritedSecretsVerbatim(t *testing.T) {
|
||||||
callerWorkflow, err := model.ReadWorkflow(strings.NewReader(`
|
callerWorkflow, err := model.ReadWorkflow(strings.NewReader(`
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ type Config struct {
|
|||||||
NoActionPatch bool // run actions exactly as published, applying no compatibility patches, see patch_actions.go
|
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
|
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
|
EventJSON string // the content of JSON file to use for event.json in containers, overrides EventPath
|
||||||
ContainerNamePrefix string // the prefix of container name
|
ContainerNamePrefix string // the prefix of container name
|
||||||
ContainerMaxLifetime time.Duration // the max lifetime of job containers
|
ContainerMaxLifetime time.Duration // the max lifetime of job containers
|
||||||
|
|||||||
@@ -350,6 +350,25 @@ func (r *Runner) cloneEnvs() map[string]string {
|
|||||||
return envs
|
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
|
// getDefaultActionsURL
|
||||||
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
|
// when DEFAULT_ACTIONS_URL == "https://github.com" and GithubMirror is not blank,
|
||||||
// it should be set to GithubMirror first.
|
// 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)
|
r.reportSetup(reporter, task)
|
||||||
|
|
||||||
workflow, jobID, err := generateWorkflow(task)
|
workflow, jobID, err := generateWorkflow(task)
|
||||||
@@ -536,6 +556,7 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
NoSkipCheckout: true,
|
NoSkipCheckout: true,
|
||||||
DisableActEnv: r.cfg.Runner.SetActEnv != nil && !*r.cfg.Runner.SetActEnv,
|
DisableActEnv: r.cfg.Runner.SetActEnv != nil && !*r.cfg.Runner.SetActEnv,
|
||||||
PresetGitHubContext: preset,
|
PresetGitHubContext: preset,
|
||||||
|
WorkflowCallInputs: workflowCallInputs,
|
||||||
EventJSON: string(eventJSON),
|
EventJSON: string(eventJSON),
|
||||||
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id),
|
||||||
ContainerMaxLifetime: maxLifetime,
|
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::")
|
return append(lines, "::endgroup::")
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputLines lists the inputs the run was triggered with, as workflow_dispatch and workflow_call
|
// inputLines lists the inputs of the job: those Gitea resolved for a called workflow's job,
|
||||||
// carry them in the event payload. Empty when the event has none.
|
// or the ones a workflow_dispatch event carries in its payload. Empty when there are none.
|
||||||
func inputLines(fields map[string]*structpb.Value) []string {
|
func inputLines(fields map[string]*structpb.Value) []string {
|
||||||
inputs := fields["event"].GetStructValue().GetFields()["inputs"].GetStructValue().GetFields()
|
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 {
|
if len(inputs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user