forgejo/services/actions/schedule_tasks.go
Earl Warren d45e6fb777
Some checks failed
testing / frontend-checks (push) Has been skipped
testing / backend-checks (push) Has been skipped
testing / test-unit (push) Has been skipped
testing / test-mysql (push) Has been skipped
testing / test-e2e (push) Has been skipped
testing / test-sqlite (push) Has been skipped
testing / test-pgsql (push) Has been skipped
testing / test-remote-cacher (valkey) (push) Has been skipped
testing / test-remote-cacher (redis) (push) Has been skipped
testing / test-remote-cacher (redict) (push) Has been skipped
testing / test-remote-cacher (garnet) (push) Has been skipped
testing / security-check (push) Has been skipped
/ release (push) Has been cancelled
[v11.0/forgejo] fix: de-duplicate Forgejo Actions job names when needed (#8884)
**Backport: https://codeberg.org/forgejo/forgejo/pulls/8864**

The status of two jobs by the same name shadow each other, they need to be distinct. If two jobs by the same name are found, they are made distinct by adding a -<occurence number> suffix.

Resolves forgejo/forgejo#8648

(cherry picked from commit 6bc1803c70)

```
Conflicts:
	services/actions/notifier_helper.go
	services/actions/schedule_tasks.go
	services/actions/workflows.go

  trivial context conflicts

  services/actions/job_parser.go

  use "github.com/nektos/act/pkg/jobparser"
```

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8884
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-08-16 18:24:51 +02:00

154 lines
4.2 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
"fmt"
"time"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
"forgejo.org/models/unit"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
webhook_module "forgejo.org/modules/webhook"
"github.com/nektos/act/pkg/jobparser"
)
// StartScheduleTasks start the task
func StartScheduleTasks(ctx context.Context) error {
return startTasks(ctx)
}
// startTasks retrieves specifications in pages, creates a schedule task for each specification,
// and updates the specification's next run time and previous run time.
// The function returns an error if there's an issue with finding or updating the specifications.
func startTasks(ctx context.Context) error {
// Set the page size
pageSize := 50
// Retrieve specs in pages until all specs have been retrieved
now := time.Now()
for page := 1; ; page++ {
// Retrieve the specs for the current page
specs, _, err := actions_model.FindSpecs(ctx, actions_model.FindSpecOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
},
Next: now.Unix(),
})
if err != nil {
return fmt.Errorf("find specs: %w", err)
}
if err := specs.LoadRepos(ctx); err != nil {
return fmt.Errorf("LoadRepos: %w", err)
}
// Loop through each spec and create a schedule task for it
for _, row := range specs {
// cancel running jobs if the event is push
if row.Schedule.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow
if err := actions_model.CancelPreviousJobs(
ctx,
row.RepoID,
row.Schedule.Ref,
row.Schedule.WorkflowID,
webhook_module.HookEventSchedule,
); err != nil {
log.Error("CancelPreviousJobs: %v", err)
}
}
if row.Repo.IsArchived {
// Skip if the repo is archived
continue
}
cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
if err != nil {
if repo_model.IsErrUnitTypeNotExist(err) {
// Skip the actions unit of this repo is disabled.
continue
}
return fmt.Errorf("GetUnit: %w", err)
}
if cfg.ActionsConfig().IsWorkflowDisabled(row.Schedule.WorkflowID) {
continue
}
if err := CreateScheduleTask(ctx, row.Schedule); err != nil {
log.Error("CreateScheduleTask: %v", err)
return err
}
// Parse the spec
schedule, err := row.Parse()
if err != nil {
log.Error("Parse: %v", err)
return err
}
// Update the spec's next run time and previous run time
row.Prev = row.Next
row.Next = timeutil.TimeStamp(schedule.Next(now.Add(1 * time.Minute)).Unix())
if err := actions_model.UpdateScheduleSpec(ctx, row, "prev", "next"); err != nil {
log.Error("UpdateScheduleSpec: %v", err)
return err
}
}
// Stop if all specs have been retrieved
if len(specs) < pageSize {
break
}
}
return nil
}
// CreateScheduleTask creates a scheduled task from a cron action schedule.
// It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job.
func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) error {
// Create a new action run based on the schedule
run := &actions_model.ActionRun{
Title: cron.Title,
RepoID: cron.RepoID,
OwnerID: cron.OwnerID,
WorkflowID: cron.WorkflowID,
TriggerUserID: cron.TriggerUserID,
Ref: cron.Ref,
CommitSHA: cron.CommitSHA,
Event: cron.Event,
EventPayload: cron.EventPayload,
TriggerEvent: string(webhook_module.HookEventSchedule),
ScheduleID: cron.ID,
Status: actions_model.StatusWaiting,
}
vars, err := actions_model.GetVariablesOfRun(ctx, run)
if err != nil {
log.Error("GetVariablesOfRun: %v", err)
return err
}
// Parse the workflow specification from the cron schedule
workflows, err := jobParser(cron.Content, jobparser.WithVars(vars))
if err != nil {
return err
}
// Insert the action run and its associated jobs into the database
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
return err
}
// Return nil if no errors occurred
return nil
}