diff --git a/services/actions/workflows.go b/services/actions/workflows.go index a08a069cc1..d7d0f781d6 100644 --- a/services/actions/workflows.go +++ b/services/actions/workflows.go @@ -97,6 +97,8 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette title = fullWorkflowID } + // Runner expects a `map[string]string` for inputs in in the payload dispatch, but newer code in the Runner's + // jobparser library takes a map[string]any which is more directly actionable for parsing: inputs := make(map[string]string) inputsAny := make(map[string]any) if workflowDispatch := wf.WorkflowDispatchConfig(); workflowDispatch != nil { @@ -109,6 +111,12 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette } inputs[key] = value inputsAny[key] = value + // To match the behaviour of the runner when parsing map[string]string into map[string]any, check for + // boolean type inputs and convert them to booleans for expression evaluation: + // https://code.forgejo.org/forgejo/runner/src/commit/d5693e379c034a3afcb920087570d9a6e179e86e/act/runner/expression.go#L435-L439 + if input.Type == "boolean" { + inputsAny[key] = value == "true" + } } } diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index f5898682ce..19edb8b4ad 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -1009,6 +1009,64 @@ func TestActionsWorkflowDispatchDynamicMatrix(t *testing.T) { }) } +// Early in the job parsing, dynamic matrices may need to access workflow inputs. Boolean inputs need special handling +// which is what this test case covers. +func TestActionsWorkflowDispatchDynamicMatrixBooleanInput(t *testing.T) { + onApplicationRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, sha, f := tests.CreateDeclarativeRepo(t, user2, "repo-workflow-dispatch", + []unit_model.Type{unit_model.TypeActions}, nil, + []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".forgejo/workflows/dispatch.yml", + ContentReader: strings.NewReader( + "name: test\n" + + "on:\n" + + " workflow_dispatch:\n" + + " inputs:\n" + + " win32:\n" + + " description: 'Boolean'\n" + + " required: false\n" + + " type: boolean\n" + + "jobs:\n" + + " test:\n" + + " runs-on: ubuntu-latest\n" + + " strategy:\n" + + " matrix:\n" + + " runner: ${{ fromJSON(inputs.win32 && '[\"win32\", \"win64\"]' || '[\"win64\"]') }}\n" + + " steps:\n" + + " - run: echo helloworld\n", + ), + }, + }, + ) + defer f() + + gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo) + require.NoError(t, err) + defer gitRepo.Close() + + workflow, err := actions_service.GetWorkflowFromCommit(gitRepo, "main", "dispatch.yml") + require.NoError(t, err) + assert.Equal(t, "refs/heads/main", workflow.Ref) + assert.Equal(t, sha, workflow.Commit.ID.String()) + + inputGetter := func(key string) string { + return "false" + } + + run, _, err := workflow.Dispatch(db.DefaultContext, inputGetter, repo, user2) + require.NoError(t, err) + + jobs, err := actions_model.GetRunJobsByRunID(t.Context(), run.ID) + require.NoError(t, err) + assert.Len(t, jobs, 1) + }) +} + func TestActionsWorkflowDispatchReusableWorkflow(t *testing.T) { onApplicationRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})