mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-07 02:48:42 +00:00
244 lines
6.9 KiB
Go
244 lines
6.9 KiB
Go
// Package valid contains the structs representing the atlantis.yaml config
|
|
// after it's been parsed and validated.
|
|
package valid
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/bmatcuk/doublestar/v4"
|
|
version "github.com/hashicorp/go-version"
|
|
)
|
|
|
|
// RepoCfg is the atlantis.yaml config after it's been parsed and validated.
|
|
type RepoCfg struct {
|
|
// Version is the version of the atlantis YAML file.
|
|
Version int
|
|
Projects []Project
|
|
Workflows map[string]Workflow
|
|
PolicySets PolicySets
|
|
Automerge *bool
|
|
AutoDiscover *AutoDiscover
|
|
ParallelApply *bool
|
|
ParallelPlan *bool
|
|
ParallelPolicyCheck *bool
|
|
DeleteSourceBranchOnMerge *bool
|
|
RepoLocks *RepoLocks
|
|
CustomPolicyCheck *bool
|
|
EmojiReaction string
|
|
AllowedRegexpPrefixes []string
|
|
AbortOnExecutionOrderFail bool
|
|
SilencePRComments []string
|
|
}
|
|
|
|
func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project {
|
|
var ps []Project
|
|
for _, p := range r.Projects {
|
|
if p.Dir == repoRelDir && p.Workspace == workspace {
|
|
ps = append(ps, p)
|
|
}
|
|
}
|
|
return ps
|
|
}
|
|
|
|
// FindProjectsByDir returns all projects that are in dir.
|
|
func (r RepoCfg) FindProjectsByDir(dir string) []Project {
|
|
var ps []Project
|
|
for _, p := range r.Projects {
|
|
if p.Dir == dir {
|
|
ps = append(ps, p)
|
|
}
|
|
}
|
|
return ps
|
|
}
|
|
|
|
func (r RepoCfg) FindProjectByName(name string) *Project {
|
|
for _, p := range r.Projects {
|
|
if p.Name != nil && *p.Name == name {
|
|
return &p
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindProjectsByName returns all projects that match with name.
|
|
func (r RepoCfg) FindProjectsByName(name string) []Project {
|
|
var ps []Project
|
|
sanitizedName := "^" + name + "$"
|
|
for _, p := range r.Projects {
|
|
if p.Name != nil {
|
|
if match, _ := regexp.MatchString(sanitizedName, *p.Name); match {
|
|
ps = append(ps, p)
|
|
}
|
|
}
|
|
}
|
|
// If we found more than one project then we need to make sure that the regex is allowed.
|
|
if len(ps) > 1 && !isRegexAllowed(name, r.AllowedRegexpPrefixes) {
|
|
log.Printf("Found more than one project for regex %q. This regex is not on the allow list.", name)
|
|
return nil
|
|
}
|
|
return ps
|
|
}
|
|
|
|
func isRegexAllowed(name string, allowedRegexpPrefixes []string) bool {
|
|
if len(allowedRegexpPrefixes) == 0 {
|
|
return true
|
|
}
|
|
for _, allowedRegexPrefix := range allowedRegexpPrefixes {
|
|
if strings.HasPrefix(name, allowedRegexPrefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// This function returns a final true/false decision for whether AutoDiscover is enabled
|
|
// for a repo. It takes into account the defaultAutoDiscoverMode when there is no explicit
|
|
// repo config. The defaultAutoDiscoverMode param should be understood as the default
|
|
// AutoDiscover mode as may be set via CLI params or server side repo config.
|
|
func (r RepoCfg) AutoDiscoverEnabled(defaultAutoDiscoverMode AutoDiscoverMode) bool {
|
|
autoDiscoverMode := defaultAutoDiscoverMode
|
|
if r.AutoDiscover != nil {
|
|
autoDiscoverMode = r.AutoDiscover.Mode
|
|
}
|
|
|
|
if autoDiscoverMode == AutoDiscoverAutoMode {
|
|
// AutoDiscover is enabled by default when no projects are defined
|
|
return len(r.Projects) == 0
|
|
}
|
|
|
|
return autoDiscoverMode == AutoDiscoverEnabledMode
|
|
}
|
|
|
|
func (r RepoCfg) IsPathIgnoredForAutoDiscover(path string) bool {
|
|
if r.AutoDiscover == nil || r.AutoDiscover.IgnorePaths == nil {
|
|
return false
|
|
}
|
|
for i := 0; i < len(r.AutoDiscover.IgnorePaths); i++ {
|
|
// Per documentation https://pkg.go.dev/github.com/bmatcuk/doublestar, if you run ValidatePattern()
|
|
// against a pattern, which we do, you can run MatchUnvalidated for a slight performance gain,
|
|
// and also no need to explicitly check for an error
|
|
if doublestar.MatchUnvalidated(r.AutoDiscover.IgnorePaths[i], path) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// validateWorkspaceAllowed returns an error if repoCfg defines projects in
|
|
// repoRelDir but none of them use workspace. We want this to be an error
|
|
// because if users have gone to the trouble of defining projects in repoRelDir
|
|
// then it's likely that if we're running a command for a workspace that isn't
|
|
// defined then they probably just typed the workspace name wrong.
|
|
func (r RepoCfg) ValidateWorkspaceAllowed(repoRelDir string, workspace string) error {
|
|
projects := r.FindProjectsByDir(repoRelDir)
|
|
|
|
// If that directory doesn't have any projects configured then we don't
|
|
// enforce workspace names.
|
|
if len(projects) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var configuredSpaces []string
|
|
for _, p := range projects {
|
|
if p.Workspace == workspace {
|
|
return nil
|
|
}
|
|
configuredSpaces = append(configuredSpaces, p.Workspace)
|
|
}
|
|
|
|
return fmt.Errorf(
|
|
"running commands in workspace %q is not allowed because this"+
|
|
" directory is only configured for the following workspaces: %s",
|
|
workspace,
|
|
strings.Join(configuredSpaces, ", "),
|
|
)
|
|
}
|
|
|
|
type Project struct {
|
|
Dir string
|
|
BranchRegex *regexp.Regexp
|
|
Workspace string
|
|
Name *string
|
|
WorkflowName *string
|
|
TerraformDistribution *string
|
|
TerraformVersion *version.Version
|
|
Autoplan Autoplan
|
|
PlanRequirements []string
|
|
ApplyRequirements []string
|
|
ImportRequirements []string
|
|
DependsOn []string
|
|
DeleteSourceBranchOnMerge *bool
|
|
RepoLocking *bool
|
|
RepoLocks *RepoLocks
|
|
ExecutionOrderGroup int
|
|
PolicyCheck *bool
|
|
CustomPolicyCheck *bool
|
|
SilencePRComments []string
|
|
}
|
|
|
|
// GetName returns the name of the project or an empty string if there is no
|
|
// project name.
|
|
func (p Project) GetName() string {
|
|
if p.Name != nil {
|
|
return *p.Name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Autoplan struct {
|
|
WhenModified []string
|
|
Enabled bool
|
|
}
|
|
|
|
// PostProcessRunOutputOption is an enum of options for post-processing RunCommand output
|
|
type PostProcessRunOutputOption string
|
|
|
|
const (
|
|
PostProcessRunOutputShow = "show"
|
|
PostProcessRunOutputHide = "hide"
|
|
PostProcessRunOutputStripRefreshing = "strip_refreshing"
|
|
)
|
|
|
|
type Stage struct {
|
|
Steps []Step
|
|
}
|
|
|
|
// CommandShell sets up the shell for command execution
|
|
type CommandShell struct {
|
|
Shell string
|
|
ShellArgs []string
|
|
}
|
|
|
|
func (s CommandShell) String() string {
|
|
return fmt.Sprintf("%s %s", s.Shell, strings.Join(s.ShellArgs, " "))
|
|
}
|
|
|
|
type Step struct {
|
|
StepName string
|
|
ExtraArgs []string
|
|
// RunCommand is either a custom run step or the command to run
|
|
// during an env step to populate the environment variable dynamically.
|
|
RunCommand string
|
|
// Output is option for post-processing a RunCommand output
|
|
Output PostProcessRunOutputOption
|
|
// EnvVarName is the name of the
|
|
// environment variable that should be set by this step.
|
|
EnvVarName string
|
|
// EnvVarValue is the value to set EnvVarName to.
|
|
EnvVarValue string
|
|
// The Shell to use for RunCommand execution.
|
|
RunShell *CommandShell
|
|
}
|
|
|
|
type Workflow struct {
|
|
Name string
|
|
Apply Stage
|
|
Plan Stage
|
|
PolicyCheck Stage
|
|
Import Stage
|
|
StateRm Stage
|
|
}
|