mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
refactor: move rerun logic to services (#12141)
Move the logic for handling reruns of Forgejo Action workflows and individual jobs to services. That is a prerequisite for adding the corresponding HTTP API endpoints.
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). 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.
- [ ] 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
- [ ] 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.
- [x] 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.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12141
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
(cherry picked from commit 6cd3f0263d)
This commit is contained in:
parent
4e40eede03
commit
be51c1d752
12 changed files with 1122 additions and 140 deletions
|
|
@ -257,19 +257,33 @@ func (run *ActionRun) IsDispatchedRun() bool {
|
||||||
return run.TriggerEvent == "workflow_dispatch"
|
return run.TriggerEvent == "workflow_dispatch"
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRunnable indicates whether this ActionRun can generally be run.
|
// IsValid indicates whether this ActionRun is valid and can be run.
|
||||||
func (run *ActionRun) IsRunnable() bool {
|
func (run *ActionRun) IsValid() bool {
|
||||||
return run.PreExecutionErrorCode == 0 && run.PreExecutionError == ""
|
return run.PreExecutionErrorCode == 0 && run.PreExecutionError == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanBeRerun indicates whether this ActionRun can be rerun.
|
// CanBeRerun indicates whether this ActionRun can be rerun.
|
||||||
func (run *ActionRun) CanBeRerun() bool {
|
func (run *ActionRun) CanBeRerun() bool {
|
||||||
if !run.IsRunnable() {
|
if !run.IsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return run.Status.IsDone()
|
return run.Status.IsDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (run *ActionRun) PrepareNextAttempt() error {
|
||||||
|
if run.Status != StatusUnknown && !run.Status.IsDone() {
|
||||||
|
return fmt.Errorf("cannot prepare next attempt because run %d is active: %s", run.ID, run.Status.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
run.PreviousDuration = run.Duration()
|
||||||
|
|
||||||
|
run.Status = StatusWaiting
|
||||||
|
run.Started = 0
|
||||||
|
run.Stopped = 0
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func actionsCountOpenCacheKey(repoID int64) string {
|
func actionsCountOpenCacheKey(repoID int64) string {
|
||||||
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
|
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,12 +141,16 @@ func (job *ActionRunJob) PrepareNextAttempt(initialStatus Status) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanBeRerun answers whether this ActionRunJob can be rerun. Returns true if it is done and the Run it belongs to
|
// CanBeRerun answers whether this ActionRunJob can be rerun. Returns true if it is done and the Run it belongs to
|
||||||
// is runnable. Returns false in all other cases, including when Run is nil.
|
// is valid. Returns false in all other cases.
|
||||||
func (job *ActionRunJob) CanBeRerun() bool {
|
func (job *ActionRunJob) CanBeRerun(ctx context.Context) (bool, error) {
|
||||||
if job.Run == nil || !job.Run.IsRunnable() {
|
if err := job.LoadRun(ctx); err != nil {
|
||||||
return false
|
return false, fmt.Errorf("cannot load run %d of job %d: %w", job.RunID, job.ID, err)
|
||||||
}
|
}
|
||||||
return job.Status.IsDone()
|
|
||||||
|
if !job.Run.IsValid() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return job.Status.IsDone(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
|
||||||
|
|
|
||||||
|
|
@ -428,9 +428,10 @@ func TestAllNeedsExist(t *testing.T) {
|
||||||
|
|
||||||
func TestActionRunJob_CanBeRerun(t *testing.T) {
|
func TestActionRunJob_CanBeRerun(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
job ActionRunJob
|
job ActionRunJob
|
||||||
canBeRerun bool
|
canBeRerun bool
|
||||||
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "job with unknown status",
|
name: "job with unknown status",
|
||||||
|
|
@ -468,9 +469,9 @@ func TestActionRunJob_CanBeRerun(t *testing.T) {
|
||||||
canBeRerun: false,
|
canBeRerun: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ActionRun is nil",
|
name: "ActionRun is nil",
|
||||||
job: ActionRunJob{Run: nil, Status: StatusSuccess},
|
job: ActionRunJob{ID: 12, Run: nil, Status: StatusSuccess},
|
||||||
canBeRerun: false,
|
expectedError: "cannot load run 0 of job 12",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with busy run but completed job",
|
name: "with busy run but completed job",
|
||||||
|
|
@ -489,7 +490,15 @@ func TestActionRunJob_CanBeRerun(t *testing.T) {
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
assert.Equal(t, testCase.canBeRerun, testCase.job.CanBeRerun())
|
result, err := testCase.job.CanBeRerun(t.Context())
|
||||||
|
|
||||||
|
if testCase.expectedError == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.ErrorContains(t, err, testCase.expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.canBeRerun, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,27 +93,27 @@ func TestIsManualRun(t *testing.T) {
|
||||||
assert.False(t, pushRun.IsDispatchedRun())
|
assert.False(t, pushRun.IsDispatchedRun())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionRun_IsRunnable(t *testing.T) {
|
func TestActionRun_IsValid(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
run ActionRun
|
run ActionRun
|
||||||
isRunnable bool
|
isValid bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid run",
|
name: "valid run",
|
||||||
run: ActionRun{},
|
run: ActionRun{},
|
||||||
isRunnable: true,
|
isValid: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with pre-execution error",
|
name: "with pre-execution error",
|
||||||
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput},
|
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput},
|
||||||
isRunnable: false,
|
isValid: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
assert.Equal(t, testCase.isRunnable, testCase.run.IsRunnable())
|
assert.Equal(t, testCase.isValid, testCase.run.IsValid())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -314,11 +314,16 @@ func getViewResponse(ctx *app_context.Context, req *ViewRequest, runIndex, jobIn
|
||||||
// Ah, another job is still running. Keep the frontend polling enabled then.
|
// Ah, another job is still running. Keep the frontend polling enabled then.
|
||||||
done = false
|
done = false
|
||||||
}
|
}
|
||||||
|
canBeRerun, err := v.CanBeRerun(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{
|
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{
|
||||||
ID: v.ID,
|
ID: v.ID,
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
Status: v.Status.String(),
|
Status: v.Status.String(),
|
||||||
CanRerun: v.CanBeRerun() && ctx.Repo.CanWrite(unit.TypeActions),
|
CanRerun: canBeRerun && ctx.Repo.CanWrite(unit.TypeActions),
|
||||||
Duration: v.Duration().String(),
|
Duration: v.Duration().String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -495,120 +500,48 @@ func Rerun(ctx *app_context.Context) {
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if jobIndexStr == "" && !run.CanBeRerun() {
|
|
||||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.rerun_impossible"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// can not rerun job when workflow is disabled
|
var rerunJobs []*actions_model.ActionRunJob
|
||||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
if jobIndexStr == "" { // Rerun the entire workflow.
|
||||||
cfg := cfgUnit.ActionsConfig()
|
rerunJobs, err = actions_service.RerunAllJobs(ctx, run)
|
||||||
if cfg.IsWorkflowDisabled(run.WorkflowID) {
|
} else { // Rerun a single job
|
||||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
|
job, _ := getRunJobs(ctx, runIndex, jobIndex)
|
||||||
return
|
if ctx.Written() {
|
||||||
}
|
|
||||||
|
|
||||||
// reset run's start and stop time when it is done
|
|
||||||
if run.Status.IsDone() {
|
|
||||||
run.PreviousDuration = run.Duration()
|
|
||||||
run.Started = 0
|
|
||||||
run.Stopped = 0
|
|
||||||
if err := actions_service.UpdateRun(ctx, run, "started", "stopped", "previous_duration"); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rerunJobs, err = actions_service.RerunJob(ctx, job)
|
||||||
}
|
}
|
||||||
|
|
||||||
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
|
if err != nil {
|
||||||
if ctx.Written() {
|
if errors.Is(err, actions_service.ErrRerunWorkflowInvalid) ||
|
||||||
return
|
errors.Is(err, actions_service.ErrRerunWorkflowStillRunning) {
|
||||||
}
|
ctx.JSONError(ctx.Locale.Tr("actions.workflow.rerun_impossible"))
|
||||||
|
return
|
||||||
if jobIndexStr == "" { // rerun all jobs
|
|
||||||
var redirectURL string
|
|
||||||
for _, j := range jobs {
|
|
||||||
if !j.CanBeRerun() {
|
|
||||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.job_rerun_impossible"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the job has needs, it should be set to "blocked" status to wait for other jobs
|
|
||||||
shouldBlock := len(j.Needs) > 0
|
|
||||||
if err := rerunJob(ctx, j, shouldBlock); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if redirectURL == "" {
|
|
||||||
redirectURL, err = j.HTMLURL(ctx)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, actions_service.ErrRerunWorkflowDisabled) {
|
||||||
if redirectURL != "" {
|
ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
|
||||||
ctx.JSON(http.StatusOK, &redirectObject{Redirect: redirectURL})
|
return
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "unable to determine redirectURL for job rerun")
|
|
||||||
}
|
}
|
||||||
return
|
if errors.Is(err, actions_service.ErrRerunJobStillRunning) {
|
||||||
}
|
|
||||||
|
|
||||||
rerunJobs := actions_service.GetAllRerunJobs(job, jobs)
|
|
||||||
|
|
||||||
var redirectURL string
|
|
||||||
for _, j := range rerunJobs {
|
|
||||||
if !j.CanBeRerun() {
|
|
||||||
ctx.JSONError(ctx.Locale.Tr("actions.workflow.job_rerun_impossible"))
|
ctx.JSONError(ctx.Locale.Tr("actions.workflow.job_rerun_impossible"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
// jobs other than the specified one should be set to "blocked" status
|
return
|
||||||
shouldBlock := j.JobID != job.JobID
|
|
||||||
if err := rerunJob(ctx, j, shouldBlock); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if j.JobID == job.JobID {
|
|
||||||
redirectURL, err = j.HTMLURL(ctx)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectURL != "" {
|
if len(rerunJobs) == 0 {
|
||||||
ctx.JSON(http.StatusOK, &redirectObject{Redirect: redirectURL})
|
ctx.Error(http.StatusInternalServerError, "no jobs were rerun")
|
||||||
} else {
|
return
|
||||||
ctx.Error(http.StatusInternalServerError, "unable to determine redirectURL for job rerun")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rerunJob(ctx *app_context.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
|
|
||||||
status := job.Status
|
|
||||||
if !status.IsDone() {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialStatus := actions_model.StatusWaiting
|
redirectURL, err := rerunJobs[0].HTMLURL(ctx)
|
||||||
if shouldBlock {
|
if err != nil {
|
||||||
initialStatus = actions_model.StatusBlocked
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
}
|
return
|
||||||
if err := job.PrepareNextAttempt(initialStatus); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
ctx.JSON(http.StatusOK, &redirectObject{Redirect: redirectURL})
|
||||||
_, err := actions_service.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "handle", "attempt", "task_id", "status", "started", "stopped")
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
actions_service.CreateCommitStatus(ctx, job)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logs(ctx *app_context.Context) {
|
func Logs(ctx *app_context.Context) {
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,7 @@ func TestActionsRerun(t *testing.T) {
|
||||||
runIndex: 138575,
|
runIndex: 138575,
|
||||||
jobIndex: 1,
|
jobIndex: 1,
|
||||||
expectedCode: 400,
|
expectedCode: 400,
|
||||||
expectedBody: "{\"errorMessage\":\"actions.workflow.job_rerun_impossible\",\"renderFormat\":\"html\"}\n",
|
expectedBody: "{\"errorMessage\":\"actions.workflow.rerun_impossible\",\"renderFormat\":\"html\"}\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
82
services/actions/TestRerun_RerunAllJobs/action_run.yml
Normal file
82
services/actions/TestRerun_RerunAllJobs/action_run.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
- id: 455620
|
||||||
|
title: Completed workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: test.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 110
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: 1ca7f0f9-81e7-4e27-85eb-b080c33d32d3
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 1 # success
|
||||||
|
version: 4
|
||||||
|
started: 1776279254
|
||||||
|
stopped: 1776279265
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279265
|
||||||
|
|
||||||
|
- id: 455630
|
||||||
|
title: Running workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: test.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 111
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 6 # running
|
||||||
|
version: 2
|
||||||
|
started: 1776281360
|
||||||
|
stopped: 0
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776281359
|
||||||
|
updated: 1776281367
|
||||||
|
|
||||||
|
- id: 455640
|
||||||
|
title: Invalid workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: invalid.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 112
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: 4055b3488bf960c9c9f5c1eb2d84e346faaad7dc
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 2 # failure
|
||||||
|
version: 2
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776282729
|
||||||
|
updated: 1776282729
|
||||||
|
pre_execution_error_code: 1
|
||||||
|
pre_execution_error_details:
|
||||||
|
- "yaml: unmarshal errors:\n line 7: cannot unmarshal !!map into []*model.Step"
|
||||||
|
|
||||||
|
- id: 455650
|
||||||
|
title: Workflow with dependent jobs
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: dependent.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 113
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 1 # success
|
||||||
|
version: 2
|
||||||
|
started: 1776331635
|
||||||
|
stopped: 1776331721
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331721
|
||||||
227
services/actions/TestRerun_RerunAllJobs/action_run_job.yml
Normal file
227
services/actions/TestRerun_RerunAllJobs/action_run_job.yml
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
# Jobs of completed workflow run.
|
||||||
|
- id: 683880
|
||||||
|
run_id: 455620
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: caller
|
||||||
|
attempt: 1
|
||||||
|
handle: 7ecf26bd-408d-485f-b162-f349f5fe95bc
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
caller:
|
||||||
|
name: caller
|
||||||
|
runs-on: []
|
||||||
|
if: false
|
||||||
|
__metadata:
|
||||||
|
workflow_call_inputs:
|
||||||
|
greet_target: Mona the Octocat
|
||||||
|
workflow_call_id: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller
|
||||||
|
needs: ["caller.callee"]
|
||||||
|
runs_on: []
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279265
|
||||||
|
- id: 683881
|
||||||
|
run_id: 455620
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: callee
|
||||||
|
attempt: 1
|
||||||
|
handle: 24920992-6701-48cc-872d-d017b33ee90e
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
greet_target:
|
||||||
|
default: Mona the Octocat
|
||||||
|
type: string
|
||||||
|
jobs:
|
||||||
|
caller.callee:
|
||||||
|
name: callee
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "Hello ${{ inputs.greet_target }}"
|
||||||
|
__metadata:
|
||||||
|
workflow_call_parent: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller.callee
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 989
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776279254
|
||||||
|
stopped: 1776279264
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279264
|
||||||
|
|
||||||
|
# Jobs of running workflow run.
|
||||||
|
- id: 683890
|
||||||
|
run_id: 455630
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: 41569413fe943e3f4e8e9625e14c1e01d85581af
|
||||||
|
name: caller
|
||||||
|
attempt: 1
|
||||||
|
handle: 0185f661-3b6b-4f49-8a56-f6585c9c396e
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
caller:
|
||||||
|
name: caller
|
||||||
|
runs-on: []
|
||||||
|
if: false
|
||||||
|
__metadata:
|
||||||
|
workflow_call_inputs:
|
||||||
|
greet_target: Mona the Octocat
|
||||||
|
workflow_call_id: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller
|
||||||
|
needs: ["caller.callee"]
|
||||||
|
runs_on: []
|
||||||
|
task_id: 0
|
||||||
|
status: 7 # blocked
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
created: 1776281359
|
||||||
|
updated: 1776281359
|
||||||
|
- id: 683891
|
||||||
|
run_id: 455630
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: 41569413fe943e3f4e8e9625e14c1e01d85581af
|
||||||
|
name: callee
|
||||||
|
attempt: 1
|
||||||
|
handle: 4ee3bdf7-69f6-4174-9ffd-eb4a250188bc
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
greet_target:
|
||||||
|
default: Mona the Octocat
|
||||||
|
type: string
|
||||||
|
jobs:
|
||||||
|
caller.callee:
|
||||||
|
name: callee
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "Hello ${{ inputs.greet_target }}"
|
||||||
|
__metadata:
|
||||||
|
workflow_call_parent: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller.callee
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 989
|
||||||
|
status: 6 # running
|
||||||
|
started: 1776281360
|
||||||
|
stopped: 0
|
||||||
|
created: 1776281359
|
||||||
|
updated: 1776281367
|
||||||
|
|
||||||
|
# Jobs of invalid workflow.
|
||||||
|
- id: 683900
|
||||||
|
run_id: 455640
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: 4055b3488bf960c9c9f5c1eb2d84e346faaad7dc
|
||||||
|
name: test
|
||||||
|
attempt: 1
|
||||||
|
handle: c60533ff-61b3-4f6f-9788-260911cda6ed
|
||||||
|
workflow_payload: ""
|
||||||
|
job_id:
|
||||||
|
needs: []
|
||||||
|
runs_on: []
|
||||||
|
task_id: 0
|
||||||
|
status: 2 # failure
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
created: 1776282729
|
||||||
|
updated: 1776282729
|
||||||
|
|
||||||
|
# Jobs of workflow with dependent jobs.
|
||||||
|
- id: 683910
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: lint
|
||||||
|
attempt: 1
|
||||||
|
handle: 0a721b46-36d1-4540-b070-d559cf87515b
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Linting"
|
||||||
|
job_id: lint
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331495
|
||||||
|
stopped: 1776331523
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331523
|
||||||
|
- id: 683911
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: build
|
||||||
|
attempt: 1
|
||||||
|
handle: eec6b880-b013-4426-9361-8a4ad491ae91
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Building"
|
||||||
|
job_id: build
|
||||||
|
needs: [lint]
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331693
|
||||||
|
stopped: 1776331721
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331721
|
||||||
|
- id: 683912
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: test
|
||||||
|
attempt: 1
|
||||||
|
handle: b6398b81-f644-4f4b-9ed9-0c596c090b2f
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Testing"
|
||||||
|
job_id: test
|
||||||
|
needs: [build]
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331665
|
||||||
|
stopped: 1776331693
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331693
|
||||||
82
services/actions/TestRerun_RerunJob/action_run.yml
Normal file
82
services/actions/TestRerun_RerunJob/action_run.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
- id: 455620
|
||||||
|
title: Completed workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: test.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 110
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: 1ca7f0f9-81e7-4e27-85eb-b080c33d32d3
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 1 # success
|
||||||
|
version: 4
|
||||||
|
started: 1776279254
|
||||||
|
stopped: 1776279265
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279265
|
||||||
|
|
||||||
|
- id: 455630
|
||||||
|
title: Running workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: test.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 111
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 6 # running
|
||||||
|
version: 2
|
||||||
|
started: 1776281360
|
||||||
|
stopped: 0
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776281359
|
||||||
|
updated: 1776281367
|
||||||
|
|
||||||
|
- id: 455640
|
||||||
|
title: Invalid workflow
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: invalid.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 112
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: 4055b3488bf960c9c9f5c1eb2d84e346faaad7dc
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 2 # failure
|
||||||
|
version: 2
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776282729
|
||||||
|
updated: 1776282729
|
||||||
|
pre_execution_error_code: 1
|
||||||
|
pre_execution_error_details:
|
||||||
|
- "yaml: unmarshal errors:\n line 7: cannot unmarshal !!map into []*model.Step"
|
||||||
|
|
||||||
|
- id: 455650
|
||||||
|
title: Workflow with dependent jobs
|
||||||
|
repo_id: 62 # test_workflows
|
||||||
|
owner_id: 2 # user2
|
||||||
|
workflow_id: dependent.yaml
|
||||||
|
workflow_directory: .forgejo/workflows
|
||||||
|
index: 113
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: refs/heads/main
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
event: push
|
||||||
|
event_payload: "{}"
|
||||||
|
status: 6 # running
|
||||||
|
version: 2
|
||||||
|
started: 1776331635
|
||||||
|
stopped: 0
|
||||||
|
previous_duration: 0
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331721
|
||||||
244
services/actions/TestRerun_RerunJob/action_run_job.yml
Normal file
244
services/actions/TestRerun_RerunJob/action_run_job.yml
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
# Jobs of completed workflow run.
|
||||||
|
- id: 683880
|
||||||
|
run_id: 455620
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: caller
|
||||||
|
attempt: 1
|
||||||
|
handle: 7ecf26bd-408d-485f-b162-f349f5fe95bc
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
caller:
|
||||||
|
name: caller
|
||||||
|
runs-on: []
|
||||||
|
if: false
|
||||||
|
__metadata:
|
||||||
|
workflow_call_inputs:
|
||||||
|
greet_target: Mona the Octocat
|
||||||
|
workflow_call_id: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller
|
||||||
|
needs: ["caller.callee"]
|
||||||
|
runs_on: []
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279265
|
||||||
|
- id: 683881
|
||||||
|
run_id: 455620
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: callee
|
||||||
|
attempt: 1
|
||||||
|
handle: 24920992-6701-48cc-872d-d017b33ee90e
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
greet_target:
|
||||||
|
default: Mona the Octocat
|
||||||
|
type: string
|
||||||
|
jobs:
|
||||||
|
caller.callee:
|
||||||
|
name: callee
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "Hello ${{ inputs.greet_target }}"
|
||||||
|
__metadata:
|
||||||
|
workflow_call_parent: d4f799b5a5d27e303ab97a546708c2aca6c1672b3bbc5defa0d73b251bca7955
|
||||||
|
job_id: caller.callee
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 989
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776279254
|
||||||
|
stopped: 1776279264
|
||||||
|
created: 1776263479
|
||||||
|
updated: 1776279264
|
||||||
|
|
||||||
|
# Jobs of running workflow run.
|
||||||
|
- id: 683590
|
||||||
|
run_id: 455630
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: lint
|
||||||
|
attempt: 1
|
||||||
|
handle: d114a733-b364-4c08-b15d-77b6cc21a616
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Linting"
|
||||||
|
job_id: lint
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331495
|
||||||
|
stopped: 1776331523
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331523
|
||||||
|
- id: 683591
|
||||||
|
run_id: 455630
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: build
|
||||||
|
attempt: 1
|
||||||
|
handle: afd58a3e-76e9-467a-9a9d-9d6250fcc905
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Building"
|
||||||
|
job_id: build
|
||||||
|
needs: [lint]
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331693
|
||||||
|
stopped: 1776331721
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331721
|
||||||
|
- id: 683592
|
||||||
|
run_id: 455630
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: e36900942550acf94ddd8c0a3b91cc0165b4bd02
|
||||||
|
name: test
|
||||||
|
attempt: 1
|
||||||
|
handle: 2d9438cb-5067-4aff-9adb-9c1265fb42d5
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Testing"
|
||||||
|
job_id: test
|
||||||
|
needs: [build]
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 6 # StatusRunning
|
||||||
|
started: 1776331665
|
||||||
|
stopped: 0
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331693
|
||||||
|
|
||||||
|
|
||||||
|
# Jobs of invalid workflow.
|
||||||
|
- id: 683900
|
||||||
|
run_id: 455640
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: 4055b3488bf960c9c9f5c1eb2d84e346faaad7dc
|
||||||
|
name: test
|
||||||
|
attempt: 1
|
||||||
|
handle: c60533ff-61b3-4f6f-9788-260911cda6ed
|
||||||
|
workflow_payload: ""
|
||||||
|
job_id:
|
||||||
|
needs: []
|
||||||
|
runs_on: []
|
||||||
|
task_id: 0
|
||||||
|
status: 2 # failure
|
||||||
|
started: 0
|
||||||
|
stopped: 0
|
||||||
|
created: 1776282729
|
||||||
|
updated: 1776282729
|
||||||
|
|
||||||
|
# Jobs of successful workflow run with dependent jobs.
|
||||||
|
- id: 683910
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: lint
|
||||||
|
attempt: 1
|
||||||
|
handle: 0a721b46-36d1-4540-b070-d559cf87515b
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Linting"
|
||||||
|
job_id: lint
|
||||||
|
needs: null
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331495
|
||||||
|
stopped: 1776331523
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331523
|
||||||
|
- id: 683911
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: build
|
||||||
|
attempt: 1
|
||||||
|
handle: eec6b880-b013-4426-9361-8a4ad491ae91
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Building"
|
||||||
|
job_id: build
|
||||||
|
needs: []
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331693
|
||||||
|
stopped: 1776331721
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331721
|
||||||
|
- id: 683912
|
||||||
|
run_id: 455650
|
||||||
|
repo_id: 62
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: ae36c1d75cc82cb9f9e54a86c8137ed05c3bd66e
|
||||||
|
name: test
|
||||||
|
attempt: 1
|
||||||
|
handle: b6398b81-f644-4f4b-9ed9-0c596c090b2f
|
||||||
|
workflow_payload: |
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "Testing"
|
||||||
|
job_id: test
|
||||||
|
needs: [build]
|
||||||
|
runs_on: ["ubuntu-latest"]
|
||||||
|
task_id: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1776331665
|
||||||
|
stopped: 1776331693
|
||||||
|
created: 1776331495
|
||||||
|
updated: 1776331693
|
||||||
|
|
@ -4,10 +4,30 @@
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
actions_model "forgejo.org/models/actions"
|
||||||
|
"forgejo.org/models/db"
|
||||||
|
"forgejo.org/models/unit"
|
||||||
"forgejo.org/modules/container"
|
"forgejo.org/modules/container"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrRerunWorkflowInvalid signals that the workflow cannot be run because it is invalid, for example, due to syntax
|
||||||
|
// errors.
|
||||||
|
ErrRerunWorkflowInvalid = errors.New("workflow is invalid")
|
||||||
|
// ErrRerunWorkflowDisabled indicates that the workflow cannot be run because it has been disabled by the user or
|
||||||
|
// Forgejo.
|
||||||
|
ErrRerunWorkflowDisabled = errors.New("workflow is disabled")
|
||||||
|
// ErrRerunWorkflowStillRunning signals that the workflow cannot be rerun because at least one job is still running.
|
||||||
|
ErrRerunWorkflowStillRunning = errors.New("workflow is still running")
|
||||||
|
// ErrRerunJobStillRunning signals that the job cannot be rerun because it is still running.
|
||||||
|
ErrRerunJobStillRunning = errors.New("job is still running")
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAllRerunJobs get all jobs that need to be rerun when job should be rerun
|
// GetAllRerunJobs get all jobs that need to be rerun when job should be rerun
|
||||||
|
|
@ -16,22 +36,154 @@ func GetAllRerunJobs(job *actions_model.ActionRunJob, allJobs []*actions_model.A
|
||||||
rerunJobsIDSet := make(container.Set[string])
|
rerunJobsIDSet := make(container.Set[string])
|
||||||
rerunJobsIDSet.Add(job.JobID)
|
rerunJobsIDSet.Add(job.JobID)
|
||||||
|
|
||||||
for {
|
for _, j := range allJobs {
|
||||||
found := false
|
if rerunJobsIDSet.Contains(j.JobID) {
|
||||||
for _, j := range allJobs {
|
continue
|
||||||
if rerunJobsIDSet.Contains(j.JobID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if slices.ContainsFunc(j.Needs, rerunJobsIDSet.Contains) {
|
|
||||||
found = true
|
|
||||||
rerunJobs = append(rerunJobs, j)
|
|
||||||
rerunJobsIDSet.Add(j.JobID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !found {
|
if slices.ContainsFunc(j.Needs, rerunJobsIDSet.Contains) {
|
||||||
break
|
rerunJobs = append(rerunJobs, j)
|
||||||
|
rerunJobsIDSet.Add(j.JobID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rerunJobs
|
return rerunJobs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RerunAllJobs reruns all jobs of the given run and returns them. For it to succeed, the workflow must be valid, and the
|
||||||
|
// previous run must have completed.
|
||||||
|
func RerunAllJobs(ctx context.Context, run *actions_model.ActionRun) ([]*actions_model.ActionRunJob, error) {
|
||||||
|
if !run.IsValid() {
|
||||||
|
return nil, ErrRerunWorkflowInvalid
|
||||||
|
}
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
return nil, ErrRerunWorkflowStillRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := run.LoadRepo(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load repo of run %d: %w", run.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsConfig := run.Repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||||
|
if actionsConfig.IsWorkflowDisabled(run.WorkflowID) {
|
||||||
|
return nil, ErrRerunWorkflowDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var rerunJobs []*actions_model.ActionRunJob
|
||||||
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
if run.Status != actions_model.StatusUnknown && !run.Status.IsDone() {
|
||||||
|
return fmt.Errorf("cannot prepare next attempt because run %d is active: %s", run.ID, run.Status.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
run.PreviousDuration = run.Duration()
|
||||||
|
|
||||||
|
run.Status = actions_model.StatusWaiting
|
||||||
|
run.Started = 0
|
||||||
|
run.Stopped = 0
|
||||||
|
|
||||||
|
// The columns have to be specified here to work around a xorm quirk: It won't update columns that are set to
|
||||||
|
// their zero value without AllCols().
|
||||||
|
if err := UpdateRun(ctx, run, "status", "started", "stopped", "previous_duration"); err != nil {
|
||||||
|
return fmt.Errorf("cannot update run %d: %w", run.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not load jobs of run %d: %w", run.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range jobs {
|
||||||
|
initialStatus := actions_model.StatusWaiting
|
||||||
|
if len(job.Needs) > 0 {
|
||||||
|
initialStatus = actions_model.StatusBlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rerunSingleJob(ctx, job, initialStatus); err != nil {
|
||||||
|
return fmt.Errorf("could not rerun job %d of run %d: %w", job.ID, run.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rerunJobs = append(rerunJobs, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rerunJobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RerunJob reruns the given job and all its dependent jobs. It returns all jobs that were rerun. For it to succeed, the
|
||||||
|
// workflow that defines this job must be valid, and the previous run must have completed. Dependent jobs that have not
|
||||||
|
// completed yet are ignored.
|
||||||
|
func RerunJob(ctx context.Context, job *actions_model.ActionRunJob) ([]*actions_model.ActionRunJob, error) {
|
||||||
|
if err := job.LoadAttributes(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load attributes of job %d: %w", job.ID, err)
|
||||||
|
}
|
||||||
|
if !job.Run.IsValid() {
|
||||||
|
return nil, ErrRerunWorkflowInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsConfig := job.Run.Repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||||
|
if actionsConfig.IsWorkflowDisabled(job.Run.WorkflowID) {
|
||||||
|
return nil, ErrRerunWorkflowDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if !job.Status.IsDone() {
|
||||||
|
return nil, ErrRerunJobStillRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
var rerunJobs []*actions_model.ActionRunJob
|
||||||
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not load jobs of run %d: %w", job.RunID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, jobToRerun := range GetAllRerunJobs(job, jobs) {
|
||||||
|
canBeRerun, err := jobToRerun.CanBeRerun(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot determine whether job %d can be rerun: %w", jobToRerun.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skipping jobs that cannot be rerun is wrong. They should be cancelled and rerun, instead, because they
|
||||||
|
// are dependent jobs and the old results might be worthless, anyway. But we keep that behaviour for now,
|
||||||
|
// because changing it requires more rework.
|
||||||
|
if !canBeRerun {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The job that should be rerun cannot be blocked, even if it has needs.
|
||||||
|
initialStatus := actions_model.StatusWaiting
|
||||||
|
if len(jobToRerun.Needs) > 0 && jobToRerun.ID != job.ID {
|
||||||
|
initialStatus = actions_model.StatusBlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rerunSingleJob(ctx, jobToRerun, initialStatus); err != nil {
|
||||||
|
return fmt.Errorf("cannot rerun job %d: %w", jobToRerun.ID, err)
|
||||||
|
}
|
||||||
|
rerunJobs = append(rerunJobs, jobToRerun)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rerunJobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rerunSingleJob(ctx context.Context, job *actions_model.ActionRunJob, initialStatus actions_model.Status) error {
|
||||||
|
oldStatus := job.Status
|
||||||
|
|
||||||
|
if err := job.PrepareNextAttempt(initialStatus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The columns have to be specified here to work around a xorm quirk: It won't update columns that are set to their
|
||||||
|
// zero value without AllCols().
|
||||||
|
if _, err := UpdateRunJob(ctx, job, builder.Eq{"status": oldStatus}, "handle", "attempt", "task_id", "status", "started", "stopped"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateCommitStatus(ctx, job)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,18 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
actions_model "forgejo.org/models/actions"
|
||||||
|
"forgejo.org/models/unit"
|
||||||
|
"forgejo.org/models/unittest"
|
||||||
|
"forgejo.org/modules/timeutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetAllRerunJobs(t *testing.T) {
|
func TestRerun_GetAllRerunJobs(t *testing.T) {
|
||||||
job1 := &actions_model.ActionRunJob{JobID: "job1"}
|
job1 := &actions_model.ActionRunJob{JobID: "job1"}
|
||||||
job2 := &actions_model.ActionRunJob{JobID: "job2", Needs: []string{"job1"}}
|
job2 := &actions_model.ActionRunJob{JobID: "job2", Needs: []string{"job1"}}
|
||||||
job3 := &actions_model.ActionRunJob{JobID: "job3", Needs: []string{"job2"}}
|
job3 := &actions_model.ActionRunJob{JobID: "job3", Needs: []string{"job2"}}
|
||||||
|
|
@ -46,3 +51,233 @@ func TestGetAllRerunJobs(t *testing.T) {
|
||||||
assert.ElementsMatch(t, tc.rerunJobs, rerunJobs)
|
assert.ElementsMatch(t, tc.rerunJobs, rerunJobs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRerun_RerunAllJobs(t *testing.T) {
|
||||||
|
t.Run("Reruns completed workflow", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunAllJobs")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455620})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunAllJobs(t.Context(), run)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455620})
|
||||||
|
|
||||||
|
assert.Equal(t, actions_model.StatusWaiting, run.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), run.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), run.Stopped)
|
||||||
|
assert.Equal(t, 11*time.Second, run.PreviousDuration)
|
||||||
|
|
||||||
|
assert.Len(t, rerunJobs, 2)
|
||||||
|
assert.Equal(t, int64(683880), rerunJobs[0].ID)
|
||||||
|
assert.Equal(t, int64(2), rerunJobs[0].Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusBlocked, rerunJobs[0].Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[0].Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[0].Stopped)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(683881), rerunJobs[1].ID)
|
||||||
|
assert.Equal(t, int64(2), rerunJobs[1].Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusWaiting, rerunJobs[1].Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[1].Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[1].Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if workflow running", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunAllJobs")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455630})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunAllJobs(t.Context(), run)
|
||||||
|
require.ErrorContains(t, err, "workflow is still running")
|
||||||
|
|
||||||
|
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455630})
|
||||||
|
|
||||||
|
assert.Equal(t, actions_model.StatusRunning, run.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776281360), run.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), run.Stopped)
|
||||||
|
assert.Equal(t, time.Duration(0), run.PreviousDuration)
|
||||||
|
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if workflow invalid", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunAllJobs")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455640})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunAllJobs(t.Context(), run)
|
||||||
|
require.ErrorContains(t, err, "workflow is invalid")
|
||||||
|
|
||||||
|
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455640})
|
||||||
|
|
||||||
|
assert.Equal(t, actions_model.StatusFailure, run.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), run.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), run.Stopped)
|
||||||
|
assert.Equal(t, time.Duration(0), run.PreviousDuration)
|
||||||
|
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if workflow disabled", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunAllJobs")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455620})
|
||||||
|
|
||||||
|
// Disable workflow
|
||||||
|
require.NoError(t, run.LoadAttributes(t.Context()))
|
||||||
|
actionsConfig := run.Repo.MustGetUnit(t.Context(), unit.TypeActions).ActionsConfig()
|
||||||
|
actionsConfig.DisableWorkflow(run.WorkflowID)
|
||||||
|
|
||||||
|
rerunJobs, err := RerunAllJobs(t.Context(), run)
|
||||||
|
require.ErrorContains(t, err, "workflow is disabled")
|
||||||
|
|
||||||
|
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455620})
|
||||||
|
|
||||||
|
assert.Equal(t, actions_model.StatusSuccess, run.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776279254), run.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776279265), run.Stopped)
|
||||||
|
assert.Equal(t, time.Duration(0), run.PreviousDuration)
|
||||||
|
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRerun_RerunJob(t *testing.T) {
|
||||||
|
t.Run("Rerun independent job", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683910})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, rerunJobs, 1)
|
||||||
|
assert.Equal(t, job.ID, rerunJobs[0].ID)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683910})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusWaiting, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Rerun job needed by others", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683911})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, rerunJobs, 2)
|
||||||
|
assert.Equal(t, int64(683911), rerunJobs[0].ID)
|
||||||
|
assert.Equal(t, int64(683912), rerunJobs[1].ID)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683911})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusWaiting, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Stopped)
|
||||||
|
|
||||||
|
dependentJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683912})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), dependentJob.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusBlocked, dependentJob.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), dependentJob.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), dependentJob.Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Rerun job with needs", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683912})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, rerunJobs, 1)
|
||||||
|
assert.Equal(t, int64(683912), rerunJobs[0].ID)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683912})
|
||||||
|
|
||||||
|
assert.Len(t, job.Needs, 1)
|
||||||
|
assert.Equal(t, int64(2), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusWaiting, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if workflow invalid", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683900})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.ErrorContains(t, err, "workflow is invalid")
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683900})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusFailure, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if workflow disabled", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683881})
|
||||||
|
|
||||||
|
// Disable workflow
|
||||||
|
require.NoError(t, job.LoadAttributes(t.Context()))
|
||||||
|
actionsConfig := job.Run.Repo.MustGetUnit(t.Context(), unit.TypeActions).ActionsConfig()
|
||||||
|
actionsConfig.DisableWorkflow(job.Run.WorkflowID)
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.ErrorContains(t, err, "workflow is disabled")
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683881})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusSuccess, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776279254), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776279264), job.Stopped)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error if job still running", func(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("services/actions/TestRerun_RerunJob")()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683592})
|
||||||
|
|
||||||
|
rerunJobs, err := RerunJob(t.Context(), job)
|
||||||
|
|
||||||
|
require.ErrorContains(t, err, "job is still running")
|
||||||
|
assert.Empty(t, rerunJobs)
|
||||||
|
|
||||||
|
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683592})
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), job.Attempt)
|
||||||
|
assert.Equal(t, actions_model.StatusRunning, job.Status)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(1776331665), job.Started)
|
||||||
|
assert.Equal(t, timeutil.TimeStamp(0), job.Stopped)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue