2023-05-19 21:37:57 +08:00
// 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"
2023-09-06 15:41:06 +08:00
"time"
2023-05-19 21:37:57 +08:00
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
2023-11-24 11:49:41 +08:00
"xorm.io/builder"
2023-05-19 21:37:57 +08:00
)
2023-09-06 15:41:06 +08:00
// ArtifactStatus is the status of an artifact, uploading, expired or need-delete
type ArtifactStatus int64
2023-05-19 21:37:57 +08:00
const (
2023-09-06 15:41:06 +08:00
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
2024-02-18 18:33:50 +08:00
ArtifactStatusPendingDeletion // 5, ArtifactStatusPendingDeletion is the status of an artifact that is pending deletion
ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
2023-05-19 21:37:57 +08:00
)
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" `
2023-07-21 10:42:01 +08:00
RunID int64 ` xorm:"index unique(runid_name_path)" ` // The run id of the artifact
2023-05-19 21:37:57 +08:00
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
2023-07-21 10:42:01 +08:00
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
2023-05-19 21:37:57 +08:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated index" `
2023-09-06 15:41:06 +08:00
ExpiredUnix timeutil . TimeStamp ` xorm:"index" ` // The time when the artifact will be expired
2023-05-19 21:37:57 +08:00
}
2023-09-06 15:41:06 +08:00
func CreateArtifact ( ctx context . Context , t * ActionTask , artifactName , artifactPath string , expiredDays int64 ) ( * ActionArtifact , error ) {
2023-05-19 21:37:57 +08:00
if err := t . LoadJob ( ctx ) ; err != nil {
return nil , err
}
2023-07-21 10:42:01 +08:00
artifact , err := getArtifactByNameAndPath ( ctx , t . Job . RunID , artifactName , artifactPath )
2023-05-19 21:37:57 +08:00
if errors . Is ( err , util . ErrNotExist ) {
artifact := & ActionArtifact {
2023-07-21 10:42:01 +08:00
ArtifactName : artifactName ,
ArtifactPath : artifactPath ,
RunID : t . Job . RunID ,
RunnerID : t . RunnerID ,
RepoID : t . RepoID ,
OwnerID : t . OwnerID ,
CommitSHA : t . CommitSHA ,
2023-09-06 15:41:06 +08:00
Status : int64 ( ArtifactStatusUploadPending ) ,
2024-10-18 10:36:23 +08:00
ExpiredUnix : timeutil . TimeStamp ( time . Now ( ) . Unix ( ) + timeutil . Day * expiredDays ) ,
2023-05-19 21:37:57 +08:00
}
if _ , err := db . GetEngine ( ctx ) . Insert ( artifact ) ; err != nil {
return nil , err
}
return artifact , nil
} else if err != nil {
return nil , err
}
2024-10-18 10:36:23 +08:00
if _ , err := db . GetEngine ( ctx ) . ID ( artifact . ID ) . Cols ( "expired_unix" ) . Update ( & ActionArtifact {
ExpiredUnix : timeutil . TimeStamp ( time . Now ( ) . Unix ( ) + timeutil . Day * expiredDays ) ,
} ) ; err != nil {
return nil , err
}
2023-05-19 21:37:57 +08:00
return artifact , nil
}
2023-07-21 10:42:01 +08:00
func getArtifactByNameAndPath ( ctx context . Context , runID int64 , name , fpath string ) ( * ActionArtifact , error ) {
2023-05-19 21:37:57 +08:00
var art ActionArtifact
2023-07-21 10:42:01 +08:00
has , err := db . GetEngine ( ctx ) . Where ( "run_id = ? AND artifact_name = ? AND artifact_path = ?" , runID , name , fpath ) . Get ( & art )
2023-05-19 21:37:57 +08:00
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
}
2023-11-24 11:49:41 +08:00
type FindArtifactsOptions struct {
db . ListOptions
RepoID int64
RunID int64
ArtifactName string
Status int
2023-05-19 21:37:57 +08:00
}
2023-11-24 11:49:41 +08:00
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 } )
}
2023-07-21 10:42:01 +08:00
2023-11-24 11:49:41 +08:00
return cond
2023-05-19 21:37:57 +08:00
}
2023-07-21 10:42:01 +08:00
// ActionArtifactMeta is the meta data of an artifact
type ActionArtifactMeta struct {
ArtifactName string
FileSize int64
2023-11-24 11:49:41 +08:00
Status ArtifactStatus
2023-07-21 10:42:01 +08:00
}
// 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" ) .
2023-09-06 15:41:06 +08:00
Where ( "run_id=? AND (status=? OR status=?)" , runID , ArtifactStatusUploadConfirmed , ArtifactStatusExpired ) .
2023-07-21 10:42:01 +08:00
GroupBy ( "artifact_name" ) .
2023-09-06 15:41:06 +08:00
Select ( "artifact_name, sum(file_size) as file_size, max(status) as status" ) .
2023-07-21 10:42:01 +08:00
Find ( & arts )
}
2023-09-06 15:41:06 +08:00
// 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 )
}
2024-02-18 18:33:50 +08:00
// ListPendingDeleteArtifacts returns all artifacts in pending-delete status.
// limit is the max number of artifacts to return.
func ListPendingDeleteArtifacts ( ctx context . Context , limit int ) ( [ ] * ActionArtifact , error ) {
arts := make ( [ ] * ActionArtifact , 0 , limit )
return arts , db . GetEngine ( ctx ) .
Where ( "status = ?" , ArtifactStatusPendingDeletion ) . Limit ( limit ) . Find ( & arts )
}
2023-09-06 15:41:06 +08:00
// 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
}
2024-02-18 18:33:50 +08:00
// SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it
func SetArtifactNeedDelete ( ctx context . Context , runID int64 , name string ) error {
_ , err := db . GetEngine ( ctx ) . Where ( "run_id=? AND artifact_name=? AND status = ?" , runID , name , ArtifactStatusUploadConfirmed ) . Cols ( "status" ) . Update ( & ActionArtifact { Status : int64 ( ArtifactStatusPendingDeletion ) } )
return err
}
// SetArtifactDeleted sets an artifact to deleted
func SetArtifactDeleted ( ctx context . Context , artifactID int64 ) error {
_ , err := db . GetEngine ( ctx ) . ID ( artifactID ) . Cols ( "status" ) . Update ( & ActionArtifact { Status : int64 ( ArtifactStatusDeleted ) } )
return err
}