// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT // This artifact server is inspired by https://github.com/nektos/act/blob/master/pkg/artifacts/server.go. // It updates url setting and uses ObjectStore to handle artifacts persistence. package actions import ( "context" "errors" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) // ArtifactStatus is the status of an artifact, uploading, expired or need-delete type ArtifactStatus int64 const ( ArtifactStatusUploadPending ArtifactStatus = iota + 1 // 1, ArtifactStatusUploadPending is the status of an artifact upload that is pending ArtifactStatusUploadConfirmed // 2, ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed ArtifactStatusUploadError // 3, ArtifactStatusUploadError is the status of an artifact upload that is errored ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired ) func init() { db.RegisterModel(new(ActionArtifact)) } // ActionArtifact is a file that is stored in the artifact storage. type ActionArtifact struct { ID int64 `xorm:"pk autoincr"` RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact RunnerID int64 RepoID int64 `xorm:"index"` OwnerID int64 CommitSHA string StoragePath string // The path to the artifact in the storage FileSize int64 // The size of the artifact in bytes FileCompressedSize int64 // The size of the artifact in bytes after gzip compression ContentEncoding string // The content encoding of the artifact ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired } func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string, expiredDays int64) (*ActionArtifact, error) { if err := t.LoadJob(ctx); err != nil { return nil, err } artifact, err := getArtifactByNameAndPath(ctx, t.Job.RunID, artifactName, artifactPath) if errors.Is(err, util.ErrNotExist) { artifact := &ActionArtifact{ ArtifactName: artifactName, ArtifactPath: artifactPath, RunID: t.Job.RunID, RunnerID: t.RunnerID, RepoID: t.RepoID, OwnerID: t.OwnerID, CommitSHA: t.CommitSHA, Status: int64(ArtifactStatusUploadPending), ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays), } if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { return nil, err } return artifact, nil } else if err != nil { return nil, err } return artifact, nil } func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) { var art ActionArtifact has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art) if err != nil { return nil, err } else if !has { return nil, util.ErrNotExist } return &art, nil } // UpdateArtifactByID updates an artifact by id func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) error { art.ID = id _, err := db.GetEngine(ctx).ID(id).AllCols().Update(art) return err } type FindArtifactsOptions struct { db.ListOptions RepoID int64 RunID int64 ArtifactName string Status int } func (opts FindArtifactsOptions) ToConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) } if opts.RunID > 0 { cond = cond.And(builder.Eq{"run_id": opts.RunID}) } if opts.ArtifactName != "" { cond = cond.And(builder.Eq{"artifact_name": opts.ArtifactName}) } if opts.Status > 0 { cond = cond.And(builder.Eq{"status": opts.Status}) } return cond } // ActionArtifactMeta is the meta data of an artifact type ActionArtifactMeta struct { ArtifactName string FileSize int64 Status ArtifactStatus } // ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) { arts := make([]*ActionArtifactMeta, 0, 10) return arts, db.GetEngine(ctx).Table("action_artifact"). Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired). GroupBy("artifact_name"). Select("artifact_name, sum(file_size) as file_size, max(status) as status"). Find(&arts) } // ListNeedExpiredArtifacts returns all need expired artifacts but not deleted func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) { arts := make([]*ActionArtifact, 0, 10) return arts, db.GetEngine(ctx). Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts) } // SetArtifactExpired sets an artifact to expired func SetArtifactExpired(ctx context.Context, artifactID int64) error { _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)}) return err }