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  }