code.gitea.io/gitea@v1.22.3/models/actions/artifact.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 // This artifact server is inspired by https://github.com/nektos/act/blob/master/pkg/artifacts/server.go. 5 // It updates url setting and uses ObjectStore to handle artifacts persistence. 6 7 package actions 8 9 import ( 10 "context" 11 "errors" 12 "time" 13 14 "code.gitea.io/gitea/models/db" 15 "code.gitea.io/gitea/modules/timeutil" 16 "code.gitea.io/gitea/modules/util" 17 18 "xorm.io/builder" 19 ) 20 21 // ArtifactStatus is the status of an artifact, uploading, expired or need-delete 22 type ArtifactStatus int64 23 24 const ( 25 ArtifactStatusUploadPending ArtifactStatus = iota + 1 // 1, ArtifactStatusUploadPending is the status of an artifact upload that is pending 26 ArtifactStatusUploadConfirmed // 2, ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed 27 ArtifactStatusUploadError // 3, ArtifactStatusUploadError is the status of an artifact upload that is errored 28 ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired 29 ArtifactStatusPendingDeletion // 5, ArtifactStatusPendingDeletion is the status of an artifact that is pending deletion 30 ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted 31 ) 32 33 func init() { 34 db.RegisterModel(new(ActionArtifact)) 35 } 36 37 // ActionArtifact is a file that is stored in the artifact storage. 38 type ActionArtifact struct { 39 ID int64 `xorm:"pk autoincr"` 40 RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact 41 RunnerID int64 42 RepoID int64 `xorm:"index"` 43 OwnerID int64 44 CommitSHA string 45 StoragePath string // The path to the artifact in the storage 46 FileSize int64 // The size of the artifact in bytes 47 FileCompressedSize int64 // The size of the artifact in bytes after gzip compression 48 ContentEncoding string // The content encoding of the artifact 49 ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it 50 ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it 51 Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete 52 CreatedUnix timeutil.TimeStamp `xorm:"created"` 53 UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` 54 ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired 55 } 56 57 func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string, expiredDays int64) (*ActionArtifact, error) { 58 if err := t.LoadJob(ctx); err != nil { 59 return nil, err 60 } 61 artifact, err := getArtifactByNameAndPath(ctx, t.Job.RunID, artifactName, artifactPath) 62 if errors.Is(err, util.ErrNotExist) { 63 artifact := &ActionArtifact{ 64 ArtifactName: artifactName, 65 ArtifactPath: artifactPath, 66 RunID: t.Job.RunID, 67 RunnerID: t.RunnerID, 68 RepoID: t.RepoID, 69 OwnerID: t.OwnerID, 70 CommitSHA: t.CommitSHA, 71 Status: int64(ArtifactStatusUploadPending), 72 ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays), 73 } 74 if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { 75 return nil, err 76 } 77 return artifact, nil 78 } else if err != nil { 79 return nil, err 80 } 81 return artifact, nil 82 } 83 84 func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) { 85 var art ActionArtifact 86 has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art) 87 if err != nil { 88 return nil, err 89 } else if !has { 90 return nil, util.ErrNotExist 91 } 92 return &art, nil 93 } 94 95 // UpdateArtifactByID updates an artifact by id 96 func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) error { 97 art.ID = id 98 _, err := db.GetEngine(ctx).ID(id).AllCols().Update(art) 99 return err 100 } 101 102 type FindArtifactsOptions struct { 103 db.ListOptions 104 RepoID int64 105 RunID int64 106 ArtifactName string 107 Status int 108 } 109 110 func (opts FindArtifactsOptions) ToConds() builder.Cond { 111 cond := builder.NewCond() 112 if opts.RepoID > 0 { 113 cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) 114 } 115 if opts.RunID > 0 { 116 cond = cond.And(builder.Eq{"run_id": opts.RunID}) 117 } 118 if opts.ArtifactName != "" { 119 cond = cond.And(builder.Eq{"artifact_name": opts.ArtifactName}) 120 } 121 if opts.Status > 0 { 122 cond = cond.And(builder.Eq{"status": opts.Status}) 123 } 124 125 return cond 126 } 127 128 // ActionArtifactMeta is the meta data of an artifact 129 type ActionArtifactMeta struct { 130 ArtifactName string 131 FileSize int64 132 Status ArtifactStatus 133 } 134 135 // ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run 136 func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) { 137 arts := make([]*ActionArtifactMeta, 0, 10) 138 return arts, db.GetEngine(ctx).Table("action_artifact"). 139 Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired). 140 GroupBy("artifact_name"). 141 Select("artifact_name, sum(file_size) as file_size, max(status) as status"). 142 Find(&arts) 143 } 144 145 // ListNeedExpiredArtifacts returns all need expired artifacts but not deleted 146 func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) { 147 arts := make([]*ActionArtifact, 0, 10) 148 return arts, db.GetEngine(ctx). 149 Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts) 150 } 151 152 // ListPendingDeleteArtifacts returns all artifacts in pending-delete status. 153 // limit is the max number of artifacts to return. 154 func ListPendingDeleteArtifacts(ctx context.Context, limit int) ([]*ActionArtifact, error) { 155 arts := make([]*ActionArtifact, 0, limit) 156 return arts, db.GetEngine(ctx). 157 Where("status = ?", ArtifactStatusPendingDeletion).Limit(limit).Find(&arts) 158 } 159 160 // SetArtifactExpired sets an artifact to expired 161 func SetArtifactExpired(ctx context.Context, artifactID int64) error { 162 _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)}) 163 return err 164 } 165 166 // SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it 167 func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error { 168 _, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)}) 169 return err 170 } 171 172 // SetArtifactDeleted sets an artifact to deleted 173 func SetArtifactDeleted(ctx context.Context, artifactID int64) error { 174 _, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)}) 175 return err 176 }