From 7a19d3c2bea3212d9e16c595f97c10943851ab32 Mon Sep 17 00:00:00 2001 From: markturney Date: Fri, 14 Mar 2025 16:01:15 +0000 Subject: [PATCH] feat(api): return run info for dispatched workflows (#7193) - When the API endpoint `/repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches` is used to launch a workflow, it currently returns no data; `/repos/{owner}/{repo}/actions/tasks` can be used to track the progress of a workflow, but you need at least that workflow's run_id and the quantity of its child jobs. Tracking workflow progress is especially important if you want to chain together multiple workflows that exist within different repositories, which is desired for https://codeberg.org/forgejo/forgejo/issues/6312. - Make it possible to track the progress of manually triggered workflows by modifying the `/repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches` to return a JSON object containing the triggered workflow's id and a list of its child job names. Co-authored-by: Andrii Chyrva Co-authored-by: Andrii Chyrva Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7193 Reviewed-by: Gusted Co-authored-by: markturney Co-committed-by: markturney --- modules/structs/workflow.go | 12 +++++++++ routers/api/v1/repo/action.go | 14 ++++++++-- routers/api/v1/swagger/action.go | 7 +++++ routers/web/repo/actions/manual.go | 3 ++- services/actions/workflows.go | 21 ++++++++------- templates/swagger/v1_json.tmpl | 33 +++++++++++++++++++++++ tests/integration/actions_trigger_test.go | 11 +++++++- 7 files changed, 88 insertions(+), 13 deletions(-) diff --git a/modules/structs/workflow.go b/modules/structs/workflow.go index c4429ea0a2..f8f1e5a7a5 100644 --- a/modules/structs/workflow.go +++ b/modules/structs/workflow.go @@ -12,4 +12,16 @@ type DispatchWorkflowOption struct { Ref string `json:"ref"` // Input keys and values configured in the workflow file. Inputs map[string]string `json:"inputs"` + // Flag to return the run info + // default: false + ReturnRunInfo bool `json:"return_run_info"` +} + +// DispatchWorkflowRun represents a workflow run +// swagger:model +type DispatchWorkflowRun struct { + // the workflow run id + ID int64 `json:"id"` + // the jobs name + Jobs []string `json:"jobs"` } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 3256b1544a..a2cc70f4d7 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -670,7 +670,8 @@ func DispatchWorkflow(ctx *context.APIContext) { return opt.Inputs[key] } - if err := workflow.Dispatch(ctx, inputGetter, ctx.Repo.Repository, ctx.Doer); err != nil { + run, jobs, err := workflow.Dispatch(ctx, inputGetter, ctx.Repo.Repository, ctx.Doer) + if err != nil { if actions_service.IsInputRequiredErr(err) { ctx.Error(http.StatusBadRequest, "workflow.Dispatch", err) } else { @@ -679,5 +680,14 @@ func DispatchWorkflow(ctx *context.APIContext) { return } - ctx.JSON(http.StatusNoContent, nil) + workflowRun := &api.DispatchWorkflowRun{ + ID: run.ID, + Jobs: jobs, + } + + if opt.ReturnRunInfo { + ctx.JSON(http.StatusCreated, workflowRun) + } else { + ctx.JSON(http.StatusNoContent, nil) + } } diff --git a/routers/api/v1/swagger/action.go b/routers/api/v1/swagger/action.go index e7ec6a81e4..2174b1ff17 100644 --- a/routers/api/v1/swagger/action.go +++ b/routers/api/v1/swagger/action.go @@ -39,3 +39,10 @@ type swaggerRunJobList struct { // in:body Body []*api.ActionRunJob `json:"body"` } + +// DispatchWorkflowRun is a Workflow Run after dispatching +// swagger:response DispatchWorkflowRun +type swaggerDispatchWorkflowRun struct { + // in:body + Body *api.DispatchWorkflowRun `json:"body"` +} diff --git a/routers/web/repo/actions/manual.go b/routers/web/repo/actions/manual.go index 949469fa21..285dc7ab7e 100644 --- a/routers/web/repo/actions/manual.go +++ b/routers/web/repo/actions/manual.go @@ -46,7 +46,8 @@ func ManualRunWorkflow(ctx *context_module.Context) { return ctx.Req.PostFormValue(formKey) } - if err := workflow.Dispatch(ctx, formKeyGetter, ctx.Repo.Repository, ctx.Doer); err != nil { + _, _, err = workflow.Dispatch(ctx, formKeyGetter, ctx.Repo.Repository, ctx.Doer) + if err != nil { if actions_service.IsInputRequiredErr(err) { ctx.Flash.Error(ctx.Locale.Tr("actions.workflow.dispatch.input_required", err.(actions_service.InputRequiredErr).Name)) ctx.Redirect(location) diff --git a/services/actions/workflows.go b/services/actions/workflows.go index e2fb31622a..e3e342264d 100644 --- a/services/actions/workflows.go +++ b/services/actions/workflows.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/webhook" "code.gitea.io/gitea/services/convert" @@ -49,15 +50,15 @@ type Workflow struct { type InputValueGetter func(key string) string -func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGetter, repo *repo_model.Repository, doer *user.User) error { +func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGetter, repo *repo_model.Repository, doer *user.User) (r *actions_model.ActionRun, j []string, err error) { content, err := actions.GetContentFromEntry(entry.GitEntry) if err != nil { - return err + return nil, nil, err } wf, err := act_model.ReadWorkflow(bytes.NewReader(content)) if err != nil { - return err + return nil, nil, err } fullWorkflowID := ".forgejo/workflows/" + entry.WorkflowID @@ -79,7 +80,7 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette if len(name) == 0 { name = key } - return InputRequiredErr{Name: name} + return nil, nil, InputRequiredErr{Name: name} } continue } @@ -92,9 +93,11 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette } if int64(len(inputs)) > setting.Actions.LimitDispatchInputs { - return errors.New("to many inputs") + return nil, nil, errors.New("to many inputs") } + jobNames := util.KeysOfMap(wf.Jobs) + payload := &structs.WorkflowDispatchPayload{ Inputs: inputs, Ref: entry.Ref, @@ -105,7 +108,7 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette p, err := json.Marshal(payload) if err != nil { - return err + return nil, nil, err } run := &actions_model.ActionRun{ @@ -126,15 +129,15 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette vars, err := actions_model.GetVariablesOfRun(ctx, run) if err != nil { - return err + return nil, nil, err } jobs, err := jobparser.Parse(content, jobparser.WithVars(vars)) if err != nil { - return err + return nil, nil, err } - return actions_model.InsertRun(ctx, run, jobs) + return run, jobNames, actions_model.InsertRun(ctx, run, jobs) } func GetWorkflowFromCommit(gitRepo *git.Repository, ref, workflowID string) (*Workflow, error) { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 1399442676..eb891d5a8a 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -23128,6 +23128,33 @@ "description": "Git reference for the workflow", "type": "string", "x-go-name": "Ref" + }, + "return_run_info": { + "description": "Flag to return the run info", + "type": "boolean", + "default": false, + "x-go-name": "ReturnRunInfo" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "DispatchWorkflowRun": { + "description": "DispatchWorkflowRun represents a workflow run", + "type": "object", + "properties": { + "id": { + "description": "the workflow run id", + "type": "integer", + "format": "int64", + "x-go-name": "ID" + }, + "jobs": { + "description": "the jobs name", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Jobs" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -28511,6 +28538,12 @@ } } }, + "DispatchWorkflowRun": { + "description": "DispatchWorkflowRun is a Workflow Run after dispatching", + "schema": { + "$ref": "#/definitions/DispatchWorkflowRun" + } + }, "EmailList": { "description": "EmailList", "schema": { diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 25bdc8018f..ecdf8b7076 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -752,9 +752,18 @@ func TestWorkflowDispatchEvent(t *testing.T) { return "" } - err = workflow.Dispatch(db.DefaultContext, inputGetter, repo, user2) + var r *actions_model.ActionRun + var j []string + r, j, err = workflow.Dispatch(db.DefaultContext, inputGetter, repo, user2) require.NoError(t, err) assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID})) + + assert.Equal(t, "test", r.Title) + assert.Equal(t, "dispatch.yml", r.WorkflowID) + assert.Equal(t, sha, r.CommitSHA) + assert.Equal(t, actions_module.GithubEventWorkflowDispatch, r.TriggerEvent) + assert.Len(t, j, 1) + assert.Equal(t, "test", j[0]) }) }