mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
feat: allow runners to request a particular job (#11676)
Forgejo Runner can optionally ask for a particular job. Example: `forgejo-runner one-job --handle 9d52c7d8-aebe-426b-b015-dd453aacaada`. This change adds the necessary job filtering to Forgejo. See https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/76 for the motivation and design considerations. PR for the extension of the runner protocol: https://code.forgejo.org/forgejo/actions-proto/pulls/18 Related change in Forgejo Runner with usage example: https://code.forgejo.org/forgejo/runner/pulls/1443 ## 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 for Go changes (can be removed for JavaScript changes) - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I ran... - [x] `make pr-go` before pushing ### Tests for JavaScript changes (can be removed for Go changes) - 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)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. *The decision if the pull request will be shown in the release notes is up to the mergers / release team.* The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/11676): <!--number 11676 --><!--line 0 --><!--description YWxsb3cgcnVubmVycyB0byByZXF1ZXN0IGEgcGFydGljdWxhciBqb2I=-->allow runners to request a particular job<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11676 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch> Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
This commit is contained in:
parent
30ee20582e
commit
5e1c13f50e
17 changed files with 278 additions and 36 deletions
|
|
@ -15,6 +15,7 @@ import (
|
|||
"forgejo.org/modules/util"
|
||||
|
||||
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
|
||||
gouuid "github.com/google/uuid"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
|
@ -30,6 +31,7 @@ type ActionRunJob struct {
|
|||
IsForkPullRequest bool
|
||||
Name string `xorm:"VARCHAR(255)"`
|
||||
Attempt int64
|
||||
Handle string `xorm:"unique"`
|
||||
WorkflowPayload []byte
|
||||
JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id
|
||||
Needs []string `xorm:"JSON TEXT"`
|
||||
|
|
@ -108,6 +110,12 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
|
|||
return job.Run.LoadAttributes(ctx)
|
||||
}
|
||||
|
||||
// IsRequestedByRunner returns true if this attempt of this ActionRunJob was explicitly requested by the runner or if
|
||||
// the runner expressed no preference.
|
||||
func (job *ActionRunJob) IsRequestedByRunner(handle *string) bool {
|
||||
return handle == nil || job.Handle == *handle
|
||||
}
|
||||
|
||||
func (job *ActionRunJob) ItRunsOn(labels []string) bool {
|
||||
if len(labels) == 0 || len(job.RunsOn) == 0 {
|
||||
return false
|
||||
|
|
@ -126,6 +134,7 @@ func (job *ActionRunJob) PrepareNextAttempt(initialStatus Status) error {
|
|||
job.Started = 0
|
||||
job.Stopped = 0
|
||||
job.TaskID = 0
|
||||
job.Handle = gouuid.New().String()
|
||||
job.Status = initialStatus
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -304,16 +304,21 @@ func TestRunHasOtherJobs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestActionRunJobPrepareNextAttempt(t *testing.T) {
|
||||
job := ActionRunJob{ID: 46}
|
||||
lastHandle := "original-handle"
|
||||
job := ActionRunJob{ID: 46, Handle: lastHandle}
|
||||
|
||||
err := job.PrepareNextAttempt(StatusWaiting)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, lastHandle, job.Handle)
|
||||
assert.NotEmpty(t, job.Handle)
|
||||
assert.Equal(t, int64(1), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusWaiting, job.Status)
|
||||
|
||||
lastHandle = job.Handle
|
||||
job.Started = timeutil.TimeStampNow()
|
||||
job.Stopped = timeutil.TimeStampNow()
|
||||
job.TaskID = int64(59)
|
||||
|
|
@ -322,19 +327,45 @@ func TestActionRunJobPrepareNextAttempt(t *testing.T) {
|
|||
err = job.PrepareNextAttempt(StatusBlocked)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, lastHandle, job.Handle)
|
||||
assert.NotEmpty(t, job.Handle)
|
||||
assert.Equal(t, int64(2), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusBlocked, job.Status)
|
||||
|
||||
lastHandle = job.Handle
|
||||
|
||||
// The job hasn't finished yet. Preparing a next attempt should not be possible. It should be left untouched.
|
||||
err = job.PrepareNextAttempt(StatusWaiting)
|
||||
require.ErrorContains(t, err, "cannot prepare next attempt because job 46 is active: blocked")
|
||||
|
||||
assert.Equal(t, lastHandle, job.Handle)
|
||||
assert.Equal(t, int64(2), job.Attempt)
|
||||
assert.Zero(t, job.Started)
|
||||
assert.Zero(t, job.Stopped)
|
||||
assert.Zero(t, job.TaskID)
|
||||
assert.Equal(t, StatusBlocked, job.Status)
|
||||
}
|
||||
|
||||
func TestIsRequestedByRunner(t *testing.T) {
|
||||
sameHandle := "4a1ca0be-4470-486d-8504-89b4a5ac00cf"
|
||||
differentHandle := "88423da3-67af-4f2d-9a92-a0db822697e9"
|
||||
emptyHandle := ""
|
||||
|
||||
job := &ActionRunJob{ID: 422, Attempt: 5, Handle: sameHandle}
|
||||
|
||||
assert.True(t, job.IsRequestedByRunner(nil))
|
||||
assert.True(t, job.IsRequestedByRunner(&sameHandle))
|
||||
assert.False(t, job.IsRequestedByRunner(&differentHandle))
|
||||
assert.False(t, job.IsRequestedByRunner(&emptyHandle))
|
||||
|
||||
// Old jobs that were created before the introduction of Handle do not have one.
|
||||
emptyHandleJob := &ActionRunJob{ID: 422, Attempt: 5, Handle: ""}
|
||||
|
||||
assert.True(t, emptyHandleJob.IsRequestedByRunner(nil))
|
||||
assert.True(t, emptyHandleJob.IsRequestedByRunner(&emptyHandle))
|
||||
|
||||
assert.False(t, emptyHandleJob.IsRequestedByRunner(&differentHandle))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ func GetAvailableJobsForRunner(e db.Engine, runner *ActionRunner) ([]*ActionRunJ
|
|||
return jobs, nil
|
||||
}
|
||||
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey *string) (*ActionTask, bool, error) {
|
||||
func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey, handle *string) (*ActionTask, bool, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
@ -364,9 +364,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey *
|
|||
// TODO: a more efficient way to filter labels
|
||||
var job *ActionRunJob
|
||||
log.Trace("runner labels: %v", runner.AgentLabels)
|
||||
for _, v := range jobs {
|
||||
if v.ItRunsOn(runner.AgentLabels) {
|
||||
job = v
|
||||
for _, j := range jobs {
|
||||
if j.IsRequestedByRunner(handle) && j.ItRunsOn(runner.AgentLabels) {
|
||||
job = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue