mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-25 21:09:44 +09:00
184 lines
7.5 KiB
Go
184 lines
7.5 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.dev/models/db"
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/modules/timeutil"
|
|
"gitea.dev/modules/util"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// ActionScopedWorkflowSource registers a repository as a source of scoped workflows, either for an owner (user/org) or for the whole instance.
|
|
type ActionScopedWorkflowSource struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
|
|
// OwnerID is the scope the source applies to: a user/org ID (applies to that owner's repos), or 0 for instance-level (applies to every repo).
|
|
OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
|
// SourceRepoID is the source repository providing the workflow files; always non-zero.
|
|
SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"`
|
|
|
|
// WorkflowConfigs maps a workflow ID (entry name) to its merge-gate config.
|
|
WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"`
|
|
|
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
|
}
|
|
|
|
// ScopedWorkflowConfig is one scoped workflow's config within a source registration.
|
|
type ScopedWorkflowConfig struct {
|
|
Required bool `json:"required"`
|
|
Patterns []string `json:"patterns"` // the status-check patterns that must be present and pass, only effective when Required is true
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(ActionScopedWorkflowSource))
|
|
}
|
|
|
|
// IsWorkflowRequired reports whether the given workflow ID (entry name) is marked required in this source.
|
|
func (s *ActionScopedWorkflowSource) IsWorkflowRequired(workflowID string) bool {
|
|
c, ok := s.WorkflowConfigs[workflowID]
|
|
return ok && c.Required
|
|
}
|
|
|
|
type FindScopedWorkflowSourceOpts struct {
|
|
db.ListOptions
|
|
OwnerIDs []int64
|
|
SourceRepoID int64
|
|
}
|
|
|
|
func (opts FindScopedWorkflowSourceOpts) ToOrders() string {
|
|
return "id"
|
|
}
|
|
|
|
func (opts FindScopedWorkflowSourceOpts) ToConds() builder.Cond {
|
|
cond := builder.NewCond()
|
|
if len(opts.OwnerIDs) > 0 {
|
|
cond = cond.And(builder.In("owner_id", opts.OwnerIDs))
|
|
}
|
|
if opts.SourceRepoID != 0 {
|
|
cond = cond.And(builder.Eq{"source_repo_id": opts.SourceRepoID})
|
|
}
|
|
return cond
|
|
}
|
|
|
|
// GetEffectiveScopedWorkflowSources returns the scoped-workflow sources effective for a repo owned by repoOwnerID:
|
|
// the owner's own sources plus instance-level (owner_id=0) sources.
|
|
func GetEffectiveScopedWorkflowSources(ctx context.Context, repoOwnerID int64) ([]*ActionScopedWorkflowSource, error) {
|
|
owners := []int64{0}
|
|
if repoOwnerID != 0 {
|
|
owners = append(owners, repoOwnerID)
|
|
}
|
|
return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners})
|
|
}
|
|
|
|
// IsScopedWorkflowSourceEffective reports whether sourceRepoID is a scoped-workflow source effective for a repo owned by repoOwnerID.
|
|
func IsScopedWorkflowSourceEffective(ctx context.Context, repoOwnerID, sourceRepoID int64) (bool, error) {
|
|
owners := []int64{0}
|
|
if repoOwnerID != 0 {
|
|
owners = append(owners, repoOwnerID)
|
|
}
|
|
return db.Exist[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners, SourceRepoID: sourceRepoID}.ToConds())
|
|
}
|
|
|
|
// IsWorkflowRequiredInSources reports whether workflowID from sourceRepoID is required by any of the given sources.
|
|
func IsWorkflowRequiredInSources(sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool {
|
|
for _, s := range sources {
|
|
if s.SourceRepoID == sourceRepoID && s.IsWorkflowRequired(workflowID) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ScopedStatusContextPrefix returns the source-repo prefix that makes a scoped run's commit-status context distinct from same-named workflows.
|
|
func ScopedStatusContextPrefix(ctx context.Context, sourceRepoID int64) string {
|
|
if sourceRepo, err := repo_model.GetRepositoryByID(ctx, sourceRepoID); err == nil {
|
|
return sourceRepo.FullName()
|
|
}
|
|
return fmt.Sprintf("scoped:%d", sourceRepoID)
|
|
}
|
|
|
|
// IsScopedWorkflowRequired reports whether workflowID from sourceRepoID is required for a repo owned by consumerOwnerID.
|
|
func IsScopedWorkflowRequired(ctx context.Context, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) {
|
|
sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID), nil
|
|
}
|
|
|
|
// IsScopedWorkflowOptedOutloads the consumer's effective sources then calls ScopedWorkflowOptedOut
|
|
func IsScopedWorkflowOptedOut(ctx context.Context, cfg *repo_model.ActionsConfig, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) {
|
|
if !cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID) {
|
|
return false, nil
|
|
}
|
|
sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return ScopedWorkflowOptedOut(cfg, sources, sourceRepoID, workflowID), nil
|
|
}
|
|
|
|
// ScopedWorkflowOptedOut reports whether a consumer's opt-out of (sourceRepoID, workflowID) is in effect.
|
|
func ScopedWorkflowOptedOut(cfg *repo_model.ActionsConfig, sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool {
|
|
return !IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID) && cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID)
|
|
}
|
|
|
|
// GetScopedWorkflowSourcesByOwner returns the sources an owner (user/org, or 0 for instance) registered.
|
|
func GetScopedWorkflowSourcesByOwner(ctx context.Context, ownerID int64) ([]*ActionScopedWorkflowSource, error) {
|
|
return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: []int64{ownerID}})
|
|
}
|
|
|
|
// GetScopedWorkflowSource returns the (owner, repo) source registration or a NotExist error.
|
|
func GetScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) (*ActionScopedWorkflowSource, error) {
|
|
src := &ActionScopedWorkflowSource{}
|
|
has, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Get(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, util.NewNotExistErrorf("scoped workflow source (owner %d, repo %d) does not exist", ownerID, repoID)
|
|
}
|
|
return src, nil
|
|
}
|
|
|
|
// AddScopedWorkflowSource registers repoID as a source for ownerID (no-op if already registered).
|
|
func AddScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error {
|
|
exists, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
return nil
|
|
}
|
|
if err := db.Insert(ctx, &ActionScopedWorkflowSource{OwnerID: ownerID, SourceRepoID: repoID}); err != nil {
|
|
// Re-check and treat an already-present row as the intended no-op.
|
|
if exists, existErr := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource)); existErr == nil && exists {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetScopedWorkflowSourceConfigs replaces the per-workflow merge-gate configs (workflow ID -> config).
|
|
func SetScopedWorkflowSourceConfigs(ctx context.Context, ownerID, repoID int64, configs map[string]*ScopedWorkflowConfig) error {
|
|
_, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).
|
|
Cols("workflow_configs").
|
|
Update(&ActionScopedWorkflowSource{WorkflowConfigs: configs})
|
|
return err
|
|
}
|
|
|
|
// RemoveScopedWorkflowSource removes the (owner, repo) source registration.
|
|
func RemoveScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error {
|
|
_, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Delete(new(ActionScopedWorkflowSource))
|
|
return err
|
|
}
|