Files
atlantis/server/core/config/valid/repo_cfg.go
2025-01-24 16:25:44 -05:00

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
}