diff --git a/models/actions/run_job.go b/models/actions/run_job.go index e3e3f4f85f..7d943f1410 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -287,3 +287,12 @@ func (job *ActionRunJob) IsWorkflowCallOuterJob() (bool, error) { } return jobWorkflow.Metadata.WorkflowCallID != "", nil } + +// Check whether the target job was generated as a result of expanding a reusable workflow. +func (job *ActionRunJob) IsWorkflowCallInnerJob() (bool, error) { + jobWorkflow, err := job.DecodeWorkflowPayload() + if err != nil { + return false, fmt.Errorf("failure decoding workflow payload: %w", err) + } + return jobWorkflow.Metadata.WorkflowCallParent != "", nil +} diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go index 82b055d3fa..231a17f463 100644 --- a/models/actions/run_job_test.go +++ b/models/actions/run_job_test.go @@ -196,3 +196,40 @@ func TestActionRunJob_IsWorkflowCallOuterJob(t *testing.T) { }) } } + +func TestActionRunJob_IsWorkflowCallInnerJob(t *testing.T) { + tests := []struct { + name string + job ActionRunJob + isWorkflowCallInnerJob bool + errContains string + }{ + { + name: "normal workflow", + job: ActionRunJob{WorkflowPayload: []byte("on: [workflow_dispatch]\nname: workflow")}, + isWorkflowCallInnerJob: false, + }, + { + name: "inner job", + job: ActionRunJob{WorkflowPayload: []byte("on:\n workflow_call:\nname: workflow\n__metadata:\n workflow_call_parent: b5a9f46f1f2513d7777fde50b169d323a6519e349cc175484c947ac315a209ed\n")}, + isWorkflowCallInnerJob: true, + }, + { + name: "unparseable workflow", + job: ActionRunJob{WorkflowPayload: []byte("name: []\nincomplete_runs_on: true")}, + errContains: "failure unmarshaling WorkflowPayload to SingleWorkflow: yaml: unmarshal errors", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + isWorkflowCallInnerJob, err := tt.job.IsWorkflowCallInnerJob() + if tt.errContains != "" { + assert.ErrorContains(t, err, tt.errContains) + } else { + require.NoError(t, err) + assert.Equal(t, tt.isWorkflowCallInnerJob, isWorkflowCallInnerJob) + } + }) + } +} diff --git a/services/actions/context.go b/services/actions/context.go index 4e4bfd75ea..d982b231ff 100644 --- a/services/actions/context.go +++ b/services/actions/context.go @@ -80,9 +80,20 @@ func generateGiteaContextForRun(run *actions_model.ActionRun) *model.GithubConte // GenerateGiteaContext generate the gitea context without token and gitea_runtime_token // job can be nil when generating a context for parsing workflow-level expressions -func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) map[string]any { +func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) (map[string]any, error) { gitContextObj := generateGiteaContextForRun(run) + if job != nil { + // Setting the `github.event_name` value to `workflow_call` while executing a reusable workflow's inner job + // causes forgejo-runner to read `on.workflow_call.inputs` and populate its values into the `inputs` context. + workflowCall, err := job.IsWorkflowCallInnerJob() + if err != nil { + return nil, fmt.Errorf("failed to inspect workflow call state: %w", err) + } else if workflowCall { + gitContextObj.EventName = "workflow_call" + } + } + gitContext, _ := githubContextToMap(gitContextObj) // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context @@ -108,7 +119,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio gitContext["run_attempt"] = fmt.Sprint(job.Attempt) } - return gitContext + return gitContext, nil } func githubContextToMap(gitContext *model.GithubContext) (map[string]any, error) { diff --git a/services/actions/context_test.go b/services/actions/context_test.go index 544543a9c3..665f207219 100644 --- a/services/actions/context_test.go +++ b/services/actions/context_test.go @@ -66,7 +66,8 @@ func TestGenerateGiteaContext(t *testing.T) { EventPayload: `{"repository": {"name": "testrepo"}}`, } - context := GenerateGiteaContext(run, nil) + context, err := GenerateGiteaContext(run, nil) + require.NoError(t, err) assert.Equal(t, "testuser", context["actor"]) assert.Equal(t, setting.AppURL+"api/v1", context["api_url"]) @@ -121,13 +122,15 @@ func TestGenerateGiteaContext(t *testing.T) { } job := &actions_model.ActionRunJob{ - ID: 100, - RunID: 1, - JobID: "test-job", - Attempt: 1, + ID: 100, + RunID: 1, + JobID: "test-job", + Attempt: 1, + WorkflowPayload: []byte("on: [push]"), } - context := GenerateGiteaContext(run, job) + context, err := GenerateGiteaContext(run, job) + require.NoError(t, err) assert.Equal(t, "test-job", context["job"]) assert.Equal(t, "1", context["run_id"]) @@ -166,7 +169,8 @@ func TestGenerateGiteaContext(t *testing.T) { EventPayload: string(payloadBytes), } - context := GenerateGiteaContext(run, nil) + context, err := GenerateGiteaContext(run, nil) + require.NoError(t, err) assert.Equal(t, "main", context["base_ref"]) assert.Equal(t, "feature-branch", context["head_ref"]) @@ -207,7 +211,8 @@ func TestGenerateGiteaContext(t *testing.T) { EventPayload: string(payloadBytes), } - context := GenerateGiteaContext(run, nil) + context, err := GenerateGiteaContext(run, nil) + require.NoError(t, err) assert.Equal(t, "main", context["base_ref"]) assert.Equal(t, "feature-branch", context["head_ref"]) @@ -218,6 +223,33 @@ func TestGenerateGiteaContext(t *testing.T) { assert.Equal(t, "branch", context["ref_type"]) assert.Equal(t, "testowner/testrepo/.github/workflows/test-workflow.yml@refs/heads/main", context["workflow_ref"]) }) + + t.Run("workflow_call job", func(t *testing.T) { + run := &actions_model.ActionRun{ + ID: 1, + Index: 42, + TriggerUser: testUser, + Repo: testRepo, + TriggerEvent: "push", + Ref: "refs/heads/main", + CommitSHA: "abc123def456", + WorkflowID: "test-workflow", + EventPayload: `{}`, + } + + job := &actions_model.ActionRunJob{ + ID: 100, + RunID: 1, + JobID: "test-job", + Attempt: 1, + WorkflowPayload: []byte("on: { workflow_call: { inputs: {} } }\n__metadata:\n workflow_call_parent: b5a9f46f1f2513d7777fde50b169d323a6519e349cc175484c947ac315a209ed\n"), + } + + context, err := GenerateGiteaContext(run, job) + require.NoError(t, err) + + assert.Equal(t, "workflow_call", context["event_name"]) + }) } func TestGenerateGiteaContextForRun(t *testing.T) { diff --git a/services/actions/task.go b/services/actions/task.go index f4ad230b0c..277c2f6ca0 100644 --- a/services/actions/task.go +++ b/services/actions/task.go @@ -88,7 +88,10 @@ func generateTaskContext(t *actions_model.ActionTask) (*structpb.Struct, error) return nil, err } - gitCtx := GenerateGiteaContext(t.Job.Run, t.Job) + gitCtx, err := GenerateGiteaContext(t.Job.Run, t.Job) + if err != nil { + return nil, err + } gitCtx["token"] = t.Token gitCtx["gitea_runtime_token"] = giteaRuntimeToken