code.gitea.io/gitea@v1.22.3/services/actions/schedule_tasks.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package actions 5 6 import ( 7 "context" 8 "fmt" 9 "time" 10 11 actions_model "code.gitea.io/gitea/models/actions" 12 "code.gitea.io/gitea/models/db" 13 repo_model "code.gitea.io/gitea/models/repo" 14 "code.gitea.io/gitea/models/unit" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/timeutil" 17 webhook_module "code.gitea.io/gitea/modules/webhook" 18 19 "github.com/nektos/act/pkg/jobparser" 20 ) 21 22 // StartScheduleTasks start the task 23 func StartScheduleTasks(ctx context.Context) error { 24 return startTasks(ctx) 25 } 26 27 // startTasks retrieves specifications in pages, creates a schedule task for each specification, 28 // and updates the specification's next run time and previous run time. 29 // The function returns an error if there's an issue with finding or updating the specifications. 30 func startTasks(ctx context.Context) error { 31 // Set the page size 32 pageSize := 50 33 34 // Retrieve specs in pages until all specs have been retrieved 35 now := time.Now() 36 for page := 1; ; page++ { 37 // Retrieve the specs for the current page 38 specs, _, err := actions_model.FindSpecs(ctx, actions_model.FindSpecOptions{ 39 ListOptions: db.ListOptions{ 40 Page: page, 41 PageSize: pageSize, 42 }, 43 Next: now.Unix(), 44 }) 45 if err != nil { 46 return fmt.Errorf("find specs: %w", err) 47 } 48 49 if err := specs.LoadRepos(ctx); err != nil { 50 return fmt.Errorf("LoadRepos: %w", err) 51 } 52 53 // Loop through each spec and create a schedule task for it 54 for _, row := range specs { 55 // cancel running jobs if the event is push 56 if row.Schedule.Event == webhook_module.HookEventPush { 57 // cancel running jobs of the same workflow 58 if err := actions_model.CancelPreviousJobs( 59 ctx, 60 row.RepoID, 61 row.Schedule.Ref, 62 row.Schedule.WorkflowID, 63 webhook_module.HookEventSchedule, 64 ); err != nil { 65 log.Error("CancelPreviousJobs: %v", err) 66 } 67 } 68 69 if row.Repo.IsArchived { 70 // Skip if the repo is archived 71 continue 72 } 73 74 cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions) 75 if err != nil { 76 if repo_model.IsErrUnitTypeNotExist(err) { 77 // Skip the actions unit of this repo is disabled. 78 continue 79 } 80 return fmt.Errorf("GetUnit: %w", err) 81 } 82 if cfg.ActionsConfig().IsWorkflowDisabled(row.Schedule.WorkflowID) { 83 continue 84 } 85 86 if err := CreateScheduleTask(ctx, row.Schedule); err != nil { 87 log.Error("CreateScheduleTask: %v", err) 88 return err 89 } 90 91 // Parse the spec 92 schedule, err := row.Parse() 93 if err != nil { 94 log.Error("Parse: %v", err) 95 return err 96 } 97 98 // Update the spec's next run time and previous run time 99 row.Prev = row.Next 100 row.Next = timeutil.TimeStamp(schedule.Next(now.Add(1 * time.Minute)).Unix()) 101 if err := actions_model.UpdateScheduleSpec(ctx, row, "prev", "next"); err != nil { 102 log.Error("UpdateScheduleSpec: %v", err) 103 return err 104 } 105 } 106 107 // Stop if all specs have been retrieved 108 if len(specs) < pageSize { 109 break 110 } 111 } 112 113 return nil 114 } 115 116 // CreateScheduleTask creates a scheduled task from a cron action schedule. 117 // It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job. 118 func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) error { 119 // Create a new action run based on the schedule 120 run := &actions_model.ActionRun{ 121 Title: cron.Title, 122 RepoID: cron.RepoID, 123 OwnerID: cron.OwnerID, 124 WorkflowID: cron.WorkflowID, 125 TriggerUserID: cron.TriggerUserID, 126 Ref: cron.Ref, 127 CommitSHA: cron.CommitSHA, 128 Event: cron.Event, 129 EventPayload: cron.EventPayload, 130 TriggerEvent: string(webhook_module.HookEventSchedule), 131 ScheduleID: cron.ID, 132 Status: actions_model.StatusWaiting, 133 } 134 135 vars, err := actions_model.GetVariablesOfRun(ctx, run) 136 if err != nil { 137 log.Error("GetVariablesOfRun: %v", err) 138 return err 139 } 140 141 // Parse the workflow specification from the cron schedule 142 workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars)) 143 if err != nil { 144 return err 145 } 146 147 // Insert the action run and its associated jobs into the database 148 if err := actions_model.InsertRun(ctx, run, workflows); err != nil { 149 return err 150 } 151 152 // Return nil if no errors occurred 153 return nil 154 }