Skip to content

Commit 6e21b4f

Browse files
committed
support outputs
1 parent 3224e23 commit 6e21b4f

File tree

3 files changed

+371
-3
lines changed

3 files changed

+371
-3
lines changed

models/actions/run_job.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,24 @@ func GetRunJobsByRunID(ctx context.Context, runID int64) (ActionJobList, error)
177177
return jobs, nil
178178
}
179179

180+
func GetReusableCallerChildJobs(ctx context.Context, callerJob *ActionRunJob) (ActionJobList, error) {
181+
if callerJob == nil || callerJob.ID <= 0 || callerJob.RunID <= 0 || callerJob.RepoID <= 0 {
182+
return nil, util.NewInvalidArgumentErrorf("invalid caller job")
183+
}
184+
var jobs []*ActionRunJob
185+
if err := db.GetEngine(ctx).
186+
Where(builder.Eq{
187+
"run_id": callerJob.RunID,
188+
"repo_id": callerJob.RepoID,
189+
"parent_call_job_id": callerJob.ID,
190+
}).
191+
OrderBy("id").
192+
Find(&jobs); err != nil {
193+
return nil, err
194+
}
195+
return jobs, nil
196+
}
197+
180198
func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, cols ...string) (int64, error) {
181199
e := db.GetEngine(ctx)
182200

services/actions/context.go

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import (
1111
actions_model "code.gitea.io/gitea/models/actions"
1212
"code.gitea.io/gitea/models/db"
1313
actions_module "code.gitea.io/gitea/modules/actions"
14+
"code.gitea.io/gitea/modules/actions/jobparser"
1415
"code.gitea.io/gitea/modules/container"
1516
"code.gitea.io/gitea/modules/git"
1617
"code.gitea.io/gitea/modules/json"
1718
"code.gitea.io/gitea/modules/setting"
19+
api "code.gitea.io/gitea/modules/structs"
1820
"code.gitea.io/gitea/modules/util"
1921

20-
"github.com/nektos/act/pkg/model"
22+
act_pkg_model "github.com/nektos/act/pkg/model"
23+
"go.yaml.in/yaml/v4"
2124
)
2225

2326
type GiteaContext map[string]any
@@ -139,12 +142,31 @@ func FindTaskNeeds(ctx context.Context, job *actions_model.ActionRunJob) (map[st
139142
}
140143

141144
ret := make(map[string]*TaskNeed, len(needs))
145+
reusableCallerOutputsCache := make(map[int64]map[string]string)
142146
for jobID, jobsWithSameID := range jobIDJobs {
143147
if !needs.Contains(jobID) {
144148
continue
145149
}
146150
var jobOutputs map[string]string
147151
for _, job := range jobsWithSameID {
152+
if job.IsReusableCall && job.TaskID == 0 && job.Status.IsDone() {
153+
outputs, ok := reusableCallerOutputsCache[job.ID]
154+
if !ok {
155+
var err error
156+
outputs, err = computeReusableCallerOutputs(ctx, job)
157+
if err != nil {
158+
return nil, err
159+
}
160+
reusableCallerOutputsCache[job.ID] = outputs
161+
}
162+
if len(jobOutputs) == 0 {
163+
jobOutputs = outputs
164+
} else {
165+
jobOutputs = mergeTwoOutputs(outputs, jobOutputs)
166+
}
167+
continue
168+
}
169+
148170
if job.TaskID == 0 || !job.Status.IsDone() {
149171
// it shouldn't happen, or the job has been rerun
150172
continue
@@ -171,6 +193,105 @@ func FindTaskNeeds(ctx context.Context, job *actions_model.ActionRunJob) (map[st
171193
return ret, nil
172194
}
173195

196+
func computeReusableCallerOutputs(ctx context.Context, callerJob *actions_model.ActionRunJob) (map[string]string, error) {
197+
cache := make(map[int64]map[string]string)
198+
return computeReusableCallerOutputsInternal(ctx, callerJob, cache)
199+
}
200+
201+
func computeReusableCallerOutputsInternal(ctx context.Context, callerJob *actions_model.ActionRunJob, cache map[int64]map[string]string) (map[string]string, error) {
202+
if callerJob == nil || !callerJob.IsReusableCall || callerJob.ReusableWorkflowUses == "" {
203+
return map[string]string{}, nil
204+
}
205+
206+
if cached, ok := cache[callerJob.ID]; ok {
207+
return cached, nil
208+
}
209+
210+
if err := callerJob.LoadAttributes(ctx); err != nil {
211+
return nil, err
212+
}
213+
214+
childJobs, err := actions_model.GetReusableCallerChildJobs(ctx, callerJob)
215+
if err != nil {
216+
return nil, fmt.Errorf("GetReusableCallerChildJobs: %w", err)
217+
}
218+
if len(childJobs) == 0 {
219+
cache[callerJob.ID] = map[string]string{}
220+
return cache[callerJob.ID], nil
221+
}
222+
for _, child := range childJobs {
223+
if !child.Status.IsDone() {
224+
cache[callerJob.ID] = map[string]string{}
225+
return cache[callerJob.ID], nil
226+
}
227+
}
228+
229+
singleWorkflow := &jobparser.SingleWorkflow{}
230+
if err := yaml.Unmarshal(childJobs[0].WorkflowPayload, singleWorkflow); err != nil {
231+
return nil, fmt.Errorf("unmarshal reusable workflow: %w", err)
232+
}
233+
workflow := &act_pkg_model.Workflow{RawOn: singleWorkflow.RawOn}
234+
235+
inputs := map[string]any{}
236+
if callerJob.CallEventPayload != "" {
237+
var payload api.WorkflowCallPayload
238+
if err := json.Unmarshal([]byte(callerJob.CallEventPayload), &payload); err != nil {
239+
return nil, fmt.Errorf("unmarshal workflow_call payload for caller job %d: %w", callerJob.ID, err)
240+
}
241+
if payload.Inputs != nil {
242+
inputs = payload.Inputs
243+
}
244+
}
245+
246+
gitCtx, err := GenerateGiteaContext(ctx, callerJob.Run, callerJob)
247+
if err != nil {
248+
return nil, err
249+
}
250+
251+
vars, err := actions_model.GetVariablesOfRun(ctx, callerJob.Run)
252+
if err != nil {
253+
return nil, fmt.Errorf("GetVariablesOfRun: %w", err)
254+
}
255+
256+
jobOutputs := make(map[string]map[string]string)
257+
for _, child := range childJobs {
258+
var outputs map[string]string
259+
switch {
260+
case child.IsReusableCall && child.ReusableWorkflowUses != "":
261+
childOutputs, err := computeReusableCallerOutputsInternal(ctx, child, cache)
262+
if err != nil {
263+
return nil, err
264+
}
265+
outputs = childOutputs
266+
case child.TaskID > 0:
267+
got, err := actions_model.FindTaskOutputByTaskID(ctx, child.TaskID)
268+
if err != nil {
269+
return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
270+
}
271+
outputs = make(map[string]string, len(got))
272+
for _, v := range got {
273+
outputs[v.OutputKey] = v.OutputValue
274+
}
275+
default:
276+
outputs = map[string]string{}
277+
}
278+
279+
if len(jobOutputs[child.JobID]) == 0 {
280+
jobOutputs[child.JobID] = outputs
281+
} else {
282+
jobOutputs[child.JobID] = mergeTwoOutputs(outputs, jobOutputs[child.JobID])
283+
}
284+
}
285+
286+
outputs, err := jobparser.EvaluateWorkflowCallOutputs(workflow, gitCtx, vars, inputs, jobOutputs)
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
cache[callerJob.ID] = outputs
292+
return outputs, nil
293+
}
294+
174295
// mergeTwoOutputs merges two outputs from two different ActionRunJobs
175296
// Values with the same output name may be overridden. The user should ensure the output names are unique.
176297
// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job
@@ -186,8 +307,8 @@ func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
186307
return ret
187308
}
188309

189-
func (g *GiteaContext) ToGitHubContext() *model.GithubContext {
190-
return &model.GithubContext{
310+
func (g *GiteaContext) ToGitHubContext() *act_pkg_model.GithubContext {
311+
return &act_pkg_model.GithubContext{
191312
Event: util.GetMapValueOrDefault(*g, "event", map[string]any(nil)),
192313
EventPath: util.GetMapValueOrDefault(*g, "event_path", ""),
193314
Workflow: util.GetMapValueOrDefault(*g, "workflow", ""),

0 commit comments

Comments
 (0)