mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Fixes #24327 to avoid the sort icon changing the table header over multiple lines and adds missing sort icons on the runners page.
		
			
				
	
	
		
			269 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package actions
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/models/db"
 | 
						|
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						|
	"code.gitea.io/gitea/models/shared/types"
 | 
						|
	user_model "code.gitea.io/gitea/models/user"
 | 
						|
	"code.gitea.io/gitea/modules/timeutil"
 | 
						|
	"code.gitea.io/gitea/modules/translation"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
 | 
						|
	runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
 | 
						|
	"xorm.io/builder"
 | 
						|
)
 | 
						|
 | 
						|
// ActionRunner represents runner machines
 | 
						|
type ActionRunner struct {
 | 
						|
	ID          int64
 | 
						|
	UUID        string                 `xorm:"CHAR(36) UNIQUE"`
 | 
						|
	Name        string                 `xorm:"VARCHAR(255)"`
 | 
						|
	Version     string                 `xorm:"VARCHAR(64)"`
 | 
						|
	OwnerID     int64                  `xorm:"index"` // org level runner, 0 means system
 | 
						|
	Owner       *user_model.User       `xorm:"-"`
 | 
						|
	RepoID      int64                  `xorm:"index"` // repo level runner, if OwnerID also is zero, then it's a global
 | 
						|
	Repo        *repo_model.Repository `xorm:"-"`
 | 
						|
	Description string                 `xorm:"TEXT"`
 | 
						|
	Base        int                    // 0 native 1 docker 2 virtual machine
 | 
						|
	RepoRange   string                 // glob match which repositories could use this runner
 | 
						|
 | 
						|
	Token     string `xorm:"-"`
 | 
						|
	TokenHash string `xorm:"UNIQUE"` // sha256 of token
 | 
						|
	TokenSalt string
 | 
						|
	// TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token
 | 
						|
 | 
						|
	LastOnline timeutil.TimeStamp `xorm:"index"`
 | 
						|
	LastActive timeutil.TimeStamp `xorm:"index"`
 | 
						|
 | 
						|
	// Store labels defined in state file (default: .runner file) of `act_runner`
 | 
						|
	AgentLabels []string `xorm:"TEXT"`
 | 
						|
 | 
						|
	Created timeutil.TimeStamp `xorm:"created"`
 | 
						|
	Updated timeutil.TimeStamp `xorm:"updated"`
 | 
						|
	Deleted timeutil.TimeStamp `xorm:"deleted"`
 | 
						|
}
 | 
						|
 | 
						|
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
 | 
						|
func (r *ActionRunner) BelongsToOwnerName() string {
 | 
						|
	if r.RepoID != 0 {
 | 
						|
		return r.Repo.FullName()
 | 
						|
	}
 | 
						|
	if r.OwnerID != 0 {
 | 
						|
		return r.Owner.Name
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
 | 
						|
	if r.RepoID != 0 {
 | 
						|
		return types.OwnerTypeRepository
 | 
						|
	}
 | 
						|
	if r.OwnerID != 0 {
 | 
						|
		if r.Owner.Type == user_model.UserTypeOrganization {
 | 
						|
			return types.OwnerTypeOrganization
 | 
						|
		} else if r.Owner.Type == user_model.UserTypeIndividual {
 | 
						|
			return types.OwnerTypeIndividual
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return types.OwnerTypeSystemGlobal
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) Status() runnerv1.RunnerStatus {
 | 
						|
	if time.Since(r.LastOnline.AsTime()) > time.Minute {
 | 
						|
		return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
 | 
						|
	}
 | 
						|
	if time.Since(r.LastActive.AsTime()) > 10*time.Second {
 | 
						|
		return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
 | 
						|
	}
 | 
						|
	return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) StatusName() string {
 | 
						|
	return strings.ToLower(strings.TrimPrefix(r.Status().String(), "RUNNER_STATUS_"))
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string {
 | 
						|
	return lang.Tr("actions.runners.status." + r.StatusName())
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) IsOnline() bool {
 | 
						|
	status := r.Status()
 | 
						|
	if status == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE || status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// Editable checks if the runner is editable by the user
 | 
						|
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
 | 
						|
	if ownerID == 0 && repoID == 0 {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	if ownerID > 0 && r.OwnerID == ownerID {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return repoID > 0 && r.RepoID == repoID
 | 
						|
}
 | 
						|
 | 
						|
// LoadAttributes loads the attributes of the runner
 | 
						|
func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
 | 
						|
	if r.OwnerID > 0 {
 | 
						|
		var user user_model.User
 | 
						|
		has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if has {
 | 
						|
			r.Owner = &user
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if r.RepoID > 0 {
 | 
						|
		var repo repo_model.Repository
 | 
						|
		has, err := db.GetEngine(ctx).ID(r.RepoID).Get(&repo)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if has {
 | 
						|
			r.Repo = &repo
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *ActionRunner) GenerateToken() (err error) {
 | 
						|
	r.Token, r.TokenSalt, r.TokenHash, _, err = generateSaltedToken()
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func init() {
 | 
						|
	db.RegisterModel(&ActionRunner{})
 | 
						|
}
 | 
						|
 | 
						|
type FindRunnerOptions struct {
 | 
						|
	db.ListOptions
 | 
						|
	RepoID        int64
 | 
						|
	OwnerID       int64
 | 
						|
	Sort          string
 | 
						|
	Filter        string
 | 
						|
	WithAvailable bool // not only runners belong to, but also runners can be used
 | 
						|
}
 | 
						|
 | 
						|
func (opts FindRunnerOptions) toCond() builder.Cond {
 | 
						|
	cond := builder.NewCond()
 | 
						|
 | 
						|
	if opts.RepoID > 0 {
 | 
						|
		c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
 | 
						|
		if opts.WithAvailable {
 | 
						|
			c = c.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": opts.RepoID})})
 | 
						|
			c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
 | 
						|
		}
 | 
						|
		cond = cond.And(c)
 | 
						|
	}
 | 
						|
	if opts.OwnerID > 0 {
 | 
						|
		c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
 | 
						|
		if opts.WithAvailable {
 | 
						|
			c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
 | 
						|
		}
 | 
						|
		cond = cond.And(c)
 | 
						|
	}
 | 
						|
 | 
						|
	if opts.Filter != "" {
 | 
						|
		cond = cond.And(builder.Like{"name", opts.Filter})
 | 
						|
	}
 | 
						|
	return cond
 | 
						|
}
 | 
						|
 | 
						|
func (opts FindRunnerOptions) toOrder() string {
 | 
						|
	switch opts.Sort {
 | 
						|
	case "online":
 | 
						|
		return "last_online DESC"
 | 
						|
	case "offline":
 | 
						|
		return "last_online ASC"
 | 
						|
	case "alphabetically":
 | 
						|
		return "name ASC"
 | 
						|
	case "reversealphabetically":
 | 
						|
		return "name DESC"
 | 
						|
	case "newest":
 | 
						|
		return "id DESC"
 | 
						|
	case "oldest":
 | 
						|
		return "id ASC"
 | 
						|
	}
 | 
						|
	return "last_online DESC"
 | 
						|
}
 | 
						|
 | 
						|
func CountRunners(ctx context.Context, opts FindRunnerOptions) (int64, error) {
 | 
						|
	return db.GetEngine(ctx).
 | 
						|
		Where(opts.toCond()).
 | 
						|
		Count(ActionRunner{})
 | 
						|
}
 | 
						|
 | 
						|
func FindRunners(ctx context.Context, opts FindRunnerOptions) (runners RunnerList, err error) {
 | 
						|
	sess := db.GetEngine(ctx).
 | 
						|
		Where(opts.toCond()).
 | 
						|
		OrderBy(opts.toOrder())
 | 
						|
	if opts.Page > 0 {
 | 
						|
		sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
 | 
						|
	}
 | 
						|
	return runners, sess.Find(&runners)
 | 
						|
}
 | 
						|
 | 
						|
// GetRunnerByUUID returns a runner via uuid
 | 
						|
func GetRunnerByUUID(ctx context.Context, uuid string) (*ActionRunner, error) {
 | 
						|
	var runner ActionRunner
 | 
						|
	has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(&runner)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	} else if !has {
 | 
						|
		return nil, fmt.Errorf("runner with uuid %s: %w", uuid, util.ErrNotExist)
 | 
						|
	}
 | 
						|
	return &runner, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetRunnerByID returns a runner via id
 | 
						|
func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
 | 
						|
	var runner ActionRunner
 | 
						|
	has, err := db.GetEngine(ctx).Where("id=?", id).Get(&runner)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	} else if !has {
 | 
						|
		return nil, fmt.Errorf("runner with id %d: %w", id, util.ErrNotExist)
 | 
						|
	}
 | 
						|
	return &runner, nil
 | 
						|
}
 | 
						|
 | 
						|
// UpdateRunner updates runner's information.
 | 
						|
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
 | 
						|
	e := db.GetEngine(ctx)
 | 
						|
	var err error
 | 
						|
	if len(cols) == 0 {
 | 
						|
		_, err = e.ID(r.ID).AllCols().Update(r)
 | 
						|
	} else {
 | 
						|
		_, err = e.ID(r.ID).Cols(cols...).Update(r)
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// DeleteRunner deletes a runner by given ID.
 | 
						|
func DeleteRunner(ctx context.Context, id int64) error {
 | 
						|
	if _, err := GetRunnerByID(ctx, id); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := db.GetEngine(ctx).Delete(&ActionRunner{ID: id})
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// CreateRunner creates new runner.
 | 
						|
func CreateRunner(ctx context.Context, t *ActionRunner) error {
 | 
						|
	_, err := db.GetEngine(ctx).Insert(t)
 | 
						|
	return err
 | 
						|
}
 |