code.gitea.io/gitea@v1.21.7/models/actions/run_job.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package actions 5 6 import ( 7 "context" 8 "fmt" 9 "slices" 10 "time" 11 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/modules/timeutil" 14 "code.gitea.io/gitea/modules/util" 15 16 "xorm.io/builder" 17 ) 18 19 // ActionRunJob represents a job of a run 20 type ActionRunJob struct { 21 ID int64 22 RunID int64 `xorm:"index"` 23 Run *ActionRun `xorm:"-"` 24 RepoID int64 `xorm:"index"` 25 OwnerID int64 `xorm:"index"` 26 CommitSHA string `xorm:"index"` 27 IsForkPullRequest bool 28 Name string `xorm:"VARCHAR(255)"` 29 Attempt int64 30 WorkflowPayload []byte 31 JobID string `xorm:"VARCHAR(255)"` // job id in workflow, not job's id 32 Needs []string `xorm:"JSON TEXT"` 33 RunsOn []string `xorm:"JSON TEXT"` 34 TaskID int64 // the latest task of the job 35 Status Status `xorm:"index"` 36 Started timeutil.TimeStamp 37 Stopped timeutil.TimeStamp 38 Created timeutil.TimeStamp `xorm:"created"` 39 Updated timeutil.TimeStamp `xorm:"updated index"` 40 } 41 42 func init() { 43 db.RegisterModel(new(ActionRunJob)) 44 } 45 46 func (job *ActionRunJob) Duration() time.Duration { 47 return calculateDuration(job.Started, job.Stopped, job.Status) 48 } 49 50 func (job *ActionRunJob) LoadRun(ctx context.Context) error { 51 if job.Run == nil { 52 run, err := GetRunByID(ctx, job.RunID) 53 if err != nil { 54 return err 55 } 56 job.Run = run 57 } 58 return nil 59 } 60 61 // LoadAttributes load Run if not loaded 62 func (job *ActionRunJob) LoadAttributes(ctx context.Context) error { 63 if job == nil { 64 return nil 65 } 66 67 if err := job.LoadRun(ctx); err != nil { 68 return err 69 } 70 71 return job.Run.LoadAttributes(ctx) 72 } 73 74 func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) { 75 var job ActionRunJob 76 has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job) 77 if err != nil { 78 return nil, err 79 } else if !has { 80 return nil, fmt.Errorf("run job with id %d: %w", id, util.ErrNotExist) 81 } 82 83 return &job, nil 84 } 85 86 func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*ActionRunJob, error) { 87 var jobs []*ActionRunJob 88 if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil { 89 return nil, err 90 } 91 return jobs, nil 92 } 93 94 func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, cols ...string) (int64, error) { 95 e := db.GetEngine(ctx) 96 97 sess := e.ID(job.ID) 98 if len(cols) > 0 { 99 sess.Cols(cols...) 100 } 101 102 if cond != nil { 103 sess.Where(cond) 104 } 105 106 affected, err := sess.Update(job) 107 if err != nil { 108 return 0, err 109 } 110 111 if affected == 0 || (!slices.Contains(cols, "status") && job.Status == 0) { 112 return affected, nil 113 } 114 115 if affected != 0 && slices.Contains(cols, "status") && job.Status.IsWaiting() { 116 // if the status of job changes to waiting again, increase tasks version. 117 if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil { 118 return 0, err 119 } 120 } 121 122 if job.RunID == 0 { 123 var err error 124 if job, err = GetRunJobByID(ctx, job.ID); err != nil { 125 return 0, err 126 } 127 } 128 129 { 130 // Other goroutines may aggregate the status of the run and update it too. 131 // So we need load the run and its jobs before updating the run. 132 run, err := GetRunByID(ctx, job.RunID) 133 if err != nil { 134 return 0, err 135 } 136 jobs, err := GetRunJobsByRunID(ctx, job.RunID) 137 if err != nil { 138 return 0, err 139 } 140 run.Status = aggregateJobStatus(jobs) 141 if run.Started.IsZero() && run.Status.IsRunning() { 142 run.Started = timeutil.TimeStampNow() 143 } 144 if run.Stopped.IsZero() && run.Status.IsDone() { 145 run.Stopped = timeutil.TimeStampNow() 146 } 147 if err := UpdateRun(ctx, run, "status", "started", "stopped"); err != nil { 148 return 0, fmt.Errorf("update run %d: %w", run.ID, err) 149 } 150 } 151 152 return affected, nil 153 } 154 155 func aggregateJobStatus(jobs []*ActionRunJob) Status { 156 allDone := true 157 allWaiting := true 158 hasFailure := false 159 for _, job := range jobs { 160 if !job.Status.IsDone() { 161 allDone = false 162 } 163 if job.Status != StatusWaiting && !job.Status.IsDone() { 164 allWaiting = false 165 } 166 if job.Status == StatusFailure || job.Status == StatusCancelled { 167 hasFailure = true 168 } 169 } 170 if allDone { 171 if hasFailure { 172 return StatusFailure 173 } 174 return StatusSuccess 175 } 176 if allWaiting { 177 return StatusWaiting 178 } 179 return StatusRunning 180 }