diff options
author | Jason Song <i@wolfogre.com> | 2023-04-23 04:12:41 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-22 16:12:41 -0400 |
commit | ac384c4e1d207a989d0f646ebc14fd0c26427d4c (patch) | |
tree | 0c262ec1540a365a01bade15d84a07eed5d4b32d /routers | |
parent | 8dc6eabbc02ef07c76671d53f28baf46871d5b68 (diff) | |
download | gitea-ac384c4e1d207a989d0f646ebc14fd0c26427d4c.tar.gz gitea-ac384c4e1d207a989d0f646ebc14fd0c26427d4c.zip |
Support upload `outputs` and use `needs` context on Actions (#24230)
See [Defining outputs for
jobs](https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs)
and [Example usage of the needs
context](https://docs.github.com/en/actions/learn-github-actions/contexts#example-usage-of-the-needs-context).
Related to:
- [actions-proto-def
#5](https://gitea.com/gitea/actions-proto-def/pulls/5)
- [act_runner #133](https://gitea.com/gitea/act_runner/pulls/133)
<details>
<summary>Tests & screenshots</summary>
Test workflow file:
```yaml
name: outputs
on: push
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.step1.outputs.output1 }}
output2: ${{ steps.step2.outputs.output2 }}
steps:
- name: step1
id: step1
run: |
date -Is > output1
cat output1
echo "output1=$(cat output1)" >> $GITHUB_OUTPUT
- name: step2
id: step2
run: |
cat /proc/sys/kernel/random/uuid > output2
cat output2
echo "output2=$(cat output2)" >> $GITHUB_OUTPUT
job2:
needs: job1
runs-on: ubuntu-latest
steps:
- run: echo ${{ needs.job1.outputs.output1 }}
- run: echo ${{ needs.job1.outputs.output2 }}
- run: echo ${{ needs.job1.result }}
```
<img width="397" alt="image"
src="https://user-images.githubusercontent.com/9418365/233313322-903e7ebf-49a7-48e2-8c17-95a4581b3284.png">
<img width="385" alt="image"
src="https://user-images.githubusercontent.com/9418365/233313442-30909135-1711-4b78-a5c6-133fcc79f47c.png">
</details>
---------
Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/actions/runner/runner.go | 28 | ||||
-rw-r--r-- | routers/api/actions/runner/utils.go | 54 |
2 files changed, 81 insertions, 1 deletions
diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go index a445864858..73c6b746a0 100644 --- a/routers/api/actions/runner/runner.go +++ b/routers/api/actions/runner/runner.go @@ -97,7 +97,7 @@ func (s *Service) Register( // FetchTask assigns a task to the runner func (s *Service) FetchTask( ctx context.Context, - req *connect.Request[runnerv1.FetchTaskRequest], + _ *connect.Request[runnerv1.FetchTaskRequest], ) (*connect.Response[runnerv1.FetchTaskResponse], error) { runner := GetRunner(ctx) @@ -145,6 +145,31 @@ func (s *Service) UpdateTask( return nil, status.Errorf(codes.Internal, "update task: %v", err) } + for k, v := range req.Msg.Outputs { + if len(k) > 255 { + log.Warn("Ignore the output of task %d because the key is too long: %q", task.ID, k) + continue + } + // The value can be a maximum of 1 MB + if l := len(v); l > 1024*1024 { + log.Warn("Ignore the output %q of task %d because the value is too long: %v", k, task.ID, l) + continue + } + // There's another limitation on GitHub that the total of all outputs in a workflow run can be a maximum of 50 MB. + // We don't check the total size here because it's not easy to do, and it doesn't really worth it. + // See https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs + + if err := actions_model.InsertTaskOutputIfNotExist(ctx, task.ID, k, v); err != nil { + log.Warn("Failed to insert the output %q of task %d: %v", k, task.ID, err) + // It's ok not to return errors, the runner will resend the outputs. + } + } + sentOutputs, err := actions_model.FindTaskOutputKeyByTaskID(ctx, task.ID) + if err != nil { + log.Warn("Failed to find the sent outputs of task %d: %v", task.ID, err) + // It's not to return errors, it can be handled when the runner resends sent outputs. + } + if err := task.LoadJob(ctx); err != nil { return nil, status.Errorf(codes.Internal, "load job: %v", err) } @@ -162,6 +187,7 @@ func (s *Service) UpdateTask( Id: req.Msg.State.Id, Result: task.Status.AsResult(), }, + SentOutputs: sentOutputs, }), nil } diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index cbfcd79a9d..705867a9b1 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -37,6 +37,17 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv Context: generateTaskContext(t), Secrets: getSecretsOfTask(ctx, t), } + + if needs, err := findTaskNeeds(ctx, t); err != nil { + log.Error("Cannot find needs for task %v: %v", t.ID, err) + // Go on with empty needs. + // If return error, the task will be wild, which means the runner will never get it when it has been assigned to the runner. + // In contrast, missing needs is less serious. + // And the task will fail and the runner will report the error in the logs. + } else { + task.Needs = needs + } + return task, true, nil } @@ -124,3 +135,46 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { return taskContext } + +func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[string]*runnerv1.TaskNeed, error) { + if err := task.LoadAttributes(ctx); err != nil { + return nil, fmt.Errorf("LoadAttributes: %w", err) + } + if len(task.Job.Needs) == 0 { + return nil, nil + } + needs := map[string]struct{}{} + for _, v := range task.Job.Needs { + needs[v] = struct{}{} + } + + jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: task.Job.RunID}) + if err != nil { + return nil, fmt.Errorf("FindRunJobs: %w", err) + } + + ret := make(map[string]*runnerv1.TaskNeed, len(needs)) + for _, job := range jobs { + if _, ok := needs[job.JobID]; !ok { + continue + } + if job.TaskID == 0 || !job.Status.IsDone() { + // it shouldn't happen, or the job has been rerun + continue + } + outputs := make(map[string]string) + got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID) + if err != nil { + return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err) + } + for _, v := range got { + outputs[v.OutputKey] = v.OutputValue + } + ret[job.JobID] = &runnerv1.TaskNeed{ + Outputs: outputs, + Result: runnerv1.Result(job.Status), + } + } + + return ret, nil +} |