Skip to content

Commit d131252

Browse files
authored
Merge branch 'main' into pr-delete
2 parents e9bb2e8 + ac384c4 commit d131252

File tree

7 files changed

+155
-4
lines changed

7 files changed

+155
-4
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module code.gitea.io/gitea
33
go 1.19
44

55
require (
6-
code.gitea.io/actions-proto-go v0.2.0
6+
code.gitea.io/actions-proto-go v0.2.1
77
code.gitea.io/gitea-vet v0.2.2
88
code.gitea.io/sdk/gitea v0.15.1
99
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
4040
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
4141
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
4242
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
43-
code.gitea.io/actions-proto-go v0.2.0 h1:nYh9nhhfk67YA4wVNLsCzd//RCvXnljwXClJ33+HPVk=
44-
code.gitea.io/actions-proto-go v0.2.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A=
43+
code.gitea.io/actions-proto-go v0.2.1 h1:ToMN/8thz2q10TuCq8dL2d8mI+/pWpJcHCvG+TELwa0=
44+
code.gitea.io/actions-proto-go v0.2.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A=
4545
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
4646
code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk=
4747
code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=

models/actions/task_output.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package actions
5+
6+
import (
7+
"context"
8+
9+
"code.gitea.io/gitea/models/db"
10+
)
11+
12+
// ActionTaskOutput represents an output of ActionTask.
13+
// So the outputs are bound to a task, that means when a completed job has been rerun,
14+
// the outputs of the job will be reset because the task is new.
15+
// It's by design, to avoid the outputs of the old task to be mixed with the new task.
16+
type ActionTaskOutput struct {
17+
ID int64
18+
TaskID int64 `xorm:"INDEX UNIQUE(task_id_output_key)"`
19+
OutputKey string `xorm:"VARCHAR(255) UNIQUE(task_id_output_key)"`
20+
OutputValue string `xorm:"MEDIUMTEXT"`
21+
}
22+
23+
// FindTaskOutputByTaskID returns the outputs of the task.
24+
func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) {
25+
var outputs []*ActionTaskOutput
26+
return outputs, db.GetEngine(ctx).Where("task_id=?", taskID).Find(&outputs)
27+
}
28+
29+
// FindTaskOutputKeyByTaskID returns the keys of the outputs of the task.
30+
func FindTaskOutputKeyByTaskID(ctx context.Context, taskID int64) ([]string, error) {
31+
var keys []string
32+
return keys, db.GetEngine(ctx).Table(ActionTaskOutput{}).Where("task_id=?", taskID).Cols("output_key").Find(&keys)
33+
}
34+
35+
// InsertTaskOutputIfNotExist inserts a new task output if it does not exist.
36+
func InsertTaskOutputIfNotExist(ctx context.Context, taskID int64, key, value string) error {
37+
return db.WithTx(ctx, func(ctx context.Context) error {
38+
sess := db.GetEngine(ctx)
39+
if exist, err := sess.Exist(&ActionTaskOutput{TaskID: taskID, OutputKey: key}); err != nil {
40+
return err
41+
} else if exist {
42+
return nil
43+
}
44+
_, err := sess.Insert(&ActionTaskOutput{
45+
TaskID: taskID,
46+
OutputKey: key,
47+
OutputValue: value,
48+
})
49+
return err
50+
})
51+
}

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,8 @@ var migrations = []Migration{
485485
NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode),
486486
// v253 -> v254
487487
NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam),
488+
// v254 -> v255
489+
NewMigration("Add ActionTaskOutput table", v1_20.AddActionTaskOutputTable),
488490
}
489491

490492
// GetCurrentDBVersion returns the current db version

models/migrations/v1_20/v254.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_20 //nolint
5+
6+
import (
7+
"xorm.io/xorm"
8+
)
9+
10+
func AddActionTaskOutputTable(x *xorm.Engine) error {
11+
type ActionTaskOutput struct {
12+
ID int64
13+
TaskID int64 `xorm:"INDEX UNIQUE(task_id_output_key)"`
14+
OutputKey string `xorm:"VARCHAR(255) UNIQUE(task_id_output_key)"`
15+
OutputValue string `xorm:"MEDIUMTEXT"`
16+
}
17+
return x.Sync(new(ActionTaskOutput))
18+
}

routers/api/actions/runner/runner.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (s *Service) Register(
9797
// FetchTask assigns a task to the runner
9898
func (s *Service) FetchTask(
9999
ctx context.Context,
100-
req *connect.Request[runnerv1.FetchTaskRequest],
100+
_ *connect.Request[runnerv1.FetchTaskRequest],
101101
) (*connect.Response[runnerv1.FetchTaskResponse], error) {
102102
runner := GetRunner(ctx)
103103

@@ -145,6 +145,31 @@ func (s *Service) UpdateTask(
145145
return nil, status.Errorf(codes.Internal, "update task: %v", err)
146146
}
147147

148+
for k, v := range req.Msg.Outputs {
149+
if len(k) > 255 {
150+
log.Warn("Ignore the output of task %d because the key is too long: %q", task.ID, k)
151+
continue
152+
}
153+
// The value can be a maximum of 1 MB
154+
if l := len(v); l > 1024*1024 {
155+
log.Warn("Ignore the output %q of task %d because the value is too long: %v", k, task.ID, l)
156+
continue
157+
}
158+
// There's another limitation on GitHub that the total of all outputs in a workflow run can be a maximum of 50 MB.
159+
// We don't check the total size here because it's not easy to do, and it doesn't really worth it.
160+
// See https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs
161+
162+
if err := actions_model.InsertTaskOutputIfNotExist(ctx, task.ID, k, v); err != nil {
163+
log.Warn("Failed to insert the output %q of task %d: %v", k, task.ID, err)
164+
// It's ok not to return errors, the runner will resend the outputs.
165+
}
166+
}
167+
sentOutputs, err := actions_model.FindTaskOutputKeyByTaskID(ctx, task.ID)
168+
if err != nil {
169+
log.Warn("Failed to find the sent outputs of task %d: %v", task.ID, err)
170+
// It's not to return errors, it can be handled when the runner resends sent outputs.
171+
}
172+
148173
if err := task.LoadJob(ctx); err != nil {
149174
return nil, status.Errorf(codes.Internal, "load job: %v", err)
150175
}
@@ -162,6 +187,7 @@ func (s *Service) UpdateTask(
162187
Id: req.Msg.State.Id,
163188
Result: task.Status.AsResult(),
164189
},
190+
SentOutputs: sentOutputs,
165191
}), nil
166192
}
167193

routers/api/actions/runner/utils.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
3737
Context: generateTaskContext(t),
3838
Secrets: getSecretsOfTask(ctx, t),
3939
}
40+
41+
if needs, err := findTaskNeeds(ctx, t); err != nil {
42+
log.Error("Cannot find needs for task %v: %v", t.ID, err)
43+
// Go on with empty needs.
44+
// If return error, the task will be wild, which means the runner will never get it when it has been assigned to the runner.
45+
// In contrast, missing needs is less serious.
46+
// And the task will fail and the runner will report the error in the logs.
47+
} else {
48+
task.Needs = needs
49+
}
50+
4051
return task, true, nil
4152
}
4253

@@ -124,3 +135,46 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
124135

125136
return taskContext
126137
}
138+
139+
func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[string]*runnerv1.TaskNeed, error) {
140+
if err := task.LoadAttributes(ctx); err != nil {
141+
return nil, fmt.Errorf("LoadAttributes: %w", err)
142+
}
143+
if len(task.Job.Needs) == 0 {
144+
return nil, nil
145+
}
146+
needs := map[string]struct{}{}
147+
for _, v := range task.Job.Needs {
148+
needs[v] = struct{}{}
149+
}
150+
151+
jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: task.Job.RunID})
152+
if err != nil {
153+
return nil, fmt.Errorf("FindRunJobs: %w", err)
154+
}
155+
156+
ret := make(map[string]*runnerv1.TaskNeed, len(needs))
157+
for _, job := range jobs {
158+
if _, ok := needs[job.JobID]; !ok {
159+
continue
160+
}
161+
if job.TaskID == 0 || !job.Status.IsDone() {
162+
// it shouldn't happen, or the job has been rerun
163+
continue
164+
}
165+
outputs := make(map[string]string)
166+
got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID)
167+
if err != nil {
168+
return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
169+
}
170+
for _, v := range got {
171+
outputs[v.OutputKey] = v.OutputValue
172+
}
173+
ret[job.JobID] = &runnerv1.TaskNeed{
174+
Outputs: outputs,
175+
Result: runnerv1.Result(job.Status),
176+
}
177+
}
178+
179+
return ret, nil
180+
}

0 commit comments

Comments
 (0)