Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions models/actions/run_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"github.com/nektos/act/pkg/jobparser"
"xorm.io/builder"
)

Expand Down Expand Up @@ -99,6 +100,24 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
return job.Run.LoadAttributes(ctx)
}

// ParseJob parses the job structure from the ActionRunJob.WorkflowPayload
func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) {
// job.WorkflowPayload is a SingleWorkflow created from an ActionRun's workflow, which exactly contains this job's YAML definition.
// Ideally it shouldn't be called "Workflow", it is just a job with global workflow fields + trigger
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
if err != nil {
return nil, fmt.Errorf("job %d single workflow: unable to parse: %w", job.ID, err)
} else if len(parsedWorkflows) != 1 {
return nil, fmt.Errorf("job %d single workflow: not single workflow", job.ID)
}
_, workflowJob := parsedWorkflows[0].Job()
if workflowJob == nil {
// it shouldn't happen, and since the callers don't check nil, so return an error instead of nil
return nil, util.ErrorWrap(util.ErrNotExist, "job %d single workflow: payload doesn't contain a job", job.ID)
}
return workflowJob, nil
}

func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
var job ActionRunJob
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
Expand Down
8 changes: 2 additions & 6 deletions models/actions/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (

runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/nektos/act/pkg/jobparser"
"google.golang.org/protobuf/types/known/timestamppb"
"xorm.io/builder"
)
Expand Down Expand Up @@ -278,13 +277,10 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
return nil, false, err
}

parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
workflowJob, err := job.ParseJob()
if err != nil {
return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err)
} else if len(parsedWorkflows) != 1 {
return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID)
return nil, false, fmt.Errorf("load job %d: %w", job.ID, err)
}
_, workflowJob := parsedWorkflows[0].Job()

if _, err := e.Insert(task); err != nil {
return nil, false, err
Expand Down
12 changes: 3 additions & 9 deletions services/actions/concurrency.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package actions

import (
"context"
"errors"
"fmt"

actions_model "code.gitea.io/gitea/models/actions"
Expand Down Expand Up @@ -91,17 +90,12 @@ func EvaluateJobConcurrencyFillModel(ctx context.Context, run *actions_model.Act
return fmt.Errorf("get inputs: %w", err)
}

// singleWorkflows is created from an ActionJob, which always contains exactly a single job's YAML definition.
// Ideally it shouldn't be called "Workflow", it is just a job with global workflow fields + trigger
singleWorkflows, err := jobparser.Parse(actionRunJob.WorkflowPayload)
workflowJob, err := actionRunJob.ParseJob()
if err != nil {
return fmt.Errorf("parse single workflow: %w", err)
} else if len(singleWorkflows) != 1 {
return errors.New("not single workflow")
return fmt.Errorf("load job %d: %w", actionRunJob.ID, err)
}
_, singleWorkflowJob := singleWorkflows[0].Job()

actionRunJob.ConcurrencyGroup, actionRunJob.ConcurrencyCancel, err = jobparser.EvaluateConcurrency(&rawConcurrency, actionRunJob.JobID, singleWorkflowJob, actionsJobCtx, jobResults, vars, inputs)
actionRunJob.ConcurrencyGroup, actionRunJob.ConcurrencyCancel, err = jobparser.EvaluateConcurrency(&rawConcurrency, actionRunJob.JobID, workflowJob, actionsJobCtx, jobResults, vars, inputs)
if err != nil {
return fmt.Errorf("evaluate concurrency: %w", err)
}
Expand Down
7 changes: 3 additions & 4 deletions services/actions/job_emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"

"github.com/nektos/act/pkg/jobparser"
"xorm.io/builder"
)

Expand Down Expand Up @@ -305,9 +304,9 @@ func (r *jobStatusResolver) resolveCheckNeeds(id int64) (allDone, allSucceed boo
}

func (r *jobStatusResolver) resolveJobHasIfCondition(actionRunJob *actions_model.ActionRunJob) (hasIf bool) {
if wfJobs, _ := jobparser.Parse(actionRunJob.WorkflowPayload); len(wfJobs) == 1 {
_, wfJob := wfJobs[0].Job()
hasIf = len(wfJob.If.Value) > 0
// FIXME evaluate this on the server side
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate more details? For example, what wrong would happen and how to evaluate on server side?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to create a more detailed refactoring proposal issue collection soon. 1

how to evaluate on server side?

This is similar to what concurrency for jobs is currently doing, Gitea references gitea/act that has all code for doing this kind of thing.

what wrong would happen

  • a to be skipped job lands in the concurrency group and prevents a non skipped job of the same group from running
  • a to be skipped job waits for a runner to become online to update the job status to skipped
    • gitea can just update the status to skipped without additional db writes, by skipping the Status Waiting.
  • all contexts should be available to the server
    • needs
    • vars
    • github/gitea
    • (env) extension

More refactoring is needed for those examples:

  • strategy (matrix) is evaluated even if the condition is false (extension matrix in if then strategy must evaluate earlier)
    • e.g. the expression in strategy depends on outputs not being set or you want to skip all
  • Reusable Workflows without runs-on should run on server as well and they should be able to deliver jobs to multiple different runners
  • jobif: ${{ cancelled() }} should be able to ignore cancel requests to be delivered to the runner
    • currently gitea force cancels every job, without a grace period of 5min

Footnotes

  1. The problems I describe are platform differences between GitHub and Gitea Actions that I would like to remove, already existing enhancements towards Actions should be unaffected

if job, err := actionRunJob.ParseJob(); err == nil {
return len(job.If.Value) > 0
}
return hasIf
}
Expand Down