mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-28 10:34:52 -04:00
feat: support workflow outputs on expanded reusable workflows (#10578)
Follow-up to #10525; adds support for `on.workflow_call.outputs` to expanded reusable workflows (when no `runs-on` is specified on a job that `uses: ...` another workflow). When all the inner jobs of a workflow call complete, `on.workflow_call.outputs` is evaluated and the related outputs are stored on the outer job's `ActionTask`. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). - **end-to-end testing**: https://code.forgejo.org/forgejo/end-to-end/pulls/1322 ### Documentation - [x] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - https://codeberg.org/forgejo/docs/pulls/1661 - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/10578): <!--number 10578 --><!--line 0 --><!--description c3VwcG9ydCB3b3JrZmxvdyBvdXRwdXRzIG9uIGV4cGFuZGVkIHJldXNhYmxlIHdvcmtmbG93cw==-->support workflow outputs on expanded reusable workflows<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10578 Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
This commit is contained in:
parent
85dacd8e50
commit
fb35abb7e1
5 changed files with 86 additions and 7 deletions
|
|
@ -234,7 +234,9 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
|||
}
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) decodeWorkflowPayload() (*jobparser.SingleWorkflow, error) {
|
||||
// Retrieves the parsed workflow for this specific job. This field is often accessed multiple times in succession, so
|
||||
// the parsed content is cached in-memory on the `ActionRunJob` instance.
|
||||
func (job *ActionRunJob) DecodeWorkflowPayload() (*jobparser.SingleWorkflow, error) {
|
||||
if job.workflowPayloadDecoded != nil {
|
||||
return job.workflowPayloadDecoded, nil
|
||||
}
|
||||
|
|
@ -259,7 +261,7 @@ func (job *ActionRunJob) ClearCachedWorkflowPayload() {
|
|||
// then regenerated and deleted. If it is incomplete, and if the information is available, the specific job and/or
|
||||
// output that causes it to be incomplete will be returned as well.
|
||||
func (job *ActionRunJob) IsIncompleteMatrix() (bool, *jobparser.IncompleteNeeds, error) {
|
||||
jobWorkflow, err := job.decodeWorkflowPayload()
|
||||
jobWorkflow, err := job.DecodeWorkflowPayload()
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("failure decoding workflow payload: %w", err)
|
||||
}
|
||||
|
|
@ -269,7 +271,7 @@ func (job *ActionRunJob) IsIncompleteMatrix() (bool, *jobparser.IncompleteNeeds,
|
|||
// Checks whether the target job has a `runs-on` field with an expression that requires an input from another job. The
|
||||
// job will be blocked until the other job is complete, and then regenerated and deleted.
|
||||
func (job *ActionRunJob) IsIncompleteRunsOn() (bool, *jobparser.IncompleteNeeds, *jobparser.IncompleteMatrix, error) {
|
||||
jobWorkflow, err := job.decodeWorkflowPayload()
|
||||
jobWorkflow, err := job.DecodeWorkflowPayload()
|
||||
if err != nil {
|
||||
return false, nil, nil, fmt.Errorf("failure decoding workflow payload: %w", err)
|
||||
}
|
||||
|
|
@ -279,7 +281,7 @@ func (job *ActionRunJob) IsIncompleteRunsOn() (bool, *jobparser.IncompleteNeeds,
|
|||
// Check whether this job is a caller of a reusable workflow -- in other words, the real work done in this job is in
|
||||
// spawned child jobs, not this job.
|
||||
func (job *ActionRunJob) IsWorkflowCallOuterJob() (bool, error) {
|
||||
jobWorkflow, err := job.decodeWorkflowPayload()
|
||||
jobWorkflow, err := job.DecodeWorkflowPayload()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failure decoding workflow payload: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# Case w/ action_run_job.id = 601
|
||||
-
|
||||
id: 100
|
||||
task_id: 100
|
||||
output_key: my_output
|
||||
output_value: 'abcdefghijklmnopqrstuvwxyz'
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Case w/ action_run_job.id = 601
|
||||
-
|
||||
id: 1001
|
||||
name: REPO_VAR
|
||||
owner_id: 0
|
||||
repo_id: 63
|
||||
data: "this is a repo variable"
|
||||
created_unix: 1737000000
|
||||
-
|
||||
id: 1002
|
||||
name: ORG_VAR
|
||||
owner_id: 2
|
||||
repo_id: 0
|
||||
data: "this is an org variable"
|
||||
created_unix: 1737000000
|
||||
-
|
||||
id: 1003
|
||||
name: GLOBAL_VAR
|
||||
owner_id: 0
|
||||
repo_id: 0
|
||||
data: "this is a global variable"
|
||||
created_unix: 1737000000
|
||||
|
|
@ -391,8 +391,49 @@ func tryHandleWorkflowCallOuterJob(ctx context.Context, job *actions_model.Actio
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Insert a placeholder task; this will be used in the future to store computed outputs
|
||||
actionTask, err := actions_model.CreatePlaceholderTask(ctx, job, map[string]string{})
|
||||
// Gather all the data that is needed to perform an expression evaluation of the job's outputs:
|
||||
singleWorkflow, err := job.DecodeWorkflowPayload()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to decode workflow payload: %w", err)
|
||||
}
|
||||
err = job.LoadRun(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to load job's run: %w", err)
|
||||
}
|
||||
err = job.Run.LoadRepo(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to load run's repo: %w", err)
|
||||
}
|
||||
githubContext := generateGiteaContextForRun(job.Run)
|
||||
taskNeeds, err := FindTaskNeeds(ctx, job)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to 'needs' for job: %w", err)
|
||||
}
|
||||
needs := make([]string, 0, len(taskNeeds))
|
||||
jobResults := make(map[string]string, len(taskNeeds))
|
||||
jobOutputs := make(map[string]map[string]string, len(taskNeeds))
|
||||
for jobID, n := range taskNeeds {
|
||||
needs = append(needs, jobID)
|
||||
jobResults[jobID] = n.Result.String()
|
||||
jobOutputs[jobID] = n.Outputs
|
||||
}
|
||||
vars, err := actions_model.GetVariablesOfRun(ctx, job.Run)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to 'var' for run: %w", err)
|
||||
}
|
||||
|
||||
// With all the required contexts, we can calculate the outputs.
|
||||
outputs := jobparser.EvaluateWorkflowCallOutputs(
|
||||
singleWorkflow,
|
||||
githubContext,
|
||||
vars,
|
||||
needs,
|
||||
jobResults,
|
||||
jobOutputs,
|
||||
)
|
||||
|
||||
// Insert a placeholder task with all the computed outputs
|
||||
actionTask, err := actions_model.CreatePlaceholderTask(ctx, job, outputs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure to insert placeholder task: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,7 +440,15 @@ func Test_tryHandleWorkflowCallOuterJob(t *testing.T) {
|
|||
name: "outputs for every context",
|
||||
runJobID: 601,
|
||||
updateFields: []string{"task_id"},
|
||||
outputs: map[string]string{},
|
||||
outputs: map[string]string{
|
||||
"from_inner_job": "abcdefghijklmnopqrstuvwxyz",
|
||||
"from_inner_job_result": "success",
|
||||
"from_forgejo_ctx": "refs/heads/main",
|
||||
"from_input_ctx": "hello, world!",
|
||||
"from_vars_repo": "this is a repo variable",
|
||||
"from_vars_org": "this is an org variable",
|
||||
"from_vars_global": "this is a global variable",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
Loading…
Reference in a new issue