diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md index 4b41068e82..7546f432a6 100644 --- a/runatlantis.io/docs/repo-level-atlantis-yaml.md +++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md @@ -71,6 +71,9 @@ workflows: steps: - run: echo hi - apply +allowed_regexp_prefixes: +- dev/ +- staging/ ``` ## Use Cases @@ -194,6 +197,7 @@ automerge: delete_source_branch_on_merge: projects: workflows: +allowed_regexp_prefixes: ``` | Key | Type | Default | Required | Description | |-------------------------------|----------------------------------------------------------|---------|----------|-------------------------------------------------------------| @@ -202,6 +206,7 @@ workflows: | delete_source_branch_on_merge | bool | `false` | no | Automatically deletes the source branch on merge | | projects | array[[Project](repo-level-atlantis-yaml.html#project)] | `[]` | no | Lists the projects in this repo | | workflows
*(restricted)* | map[string: [Workflow](custom-workflows.html#reference)] | `{}` | no | Custom workflows | +| allowed_regexp_prefixes | array[string] | `[]` | no | Lists the allowed regexp prefixes to use when the [`--enable-regexp-cmd`](server-configuration.html#enable-regexp-cmd) flag is used ### Project ```yaml diff --git a/server/events/yaml/raw/repo_cfg.go b/server/events/yaml/raw/repo_cfg.go index 3f90803bd5..444175f6da 100644 --- a/server/events/yaml/raw/repo_cfg.go +++ b/server/events/yaml/raw/repo_cfg.go @@ -32,6 +32,7 @@ type RepoCfg struct { ParallelApply *bool `yaml:"parallel_apply,omitempty"` ParallelPlan *bool `yaml:"parallel_plan,omitempty"` DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty"` + AllowedRegexpPrefixes []string `yaml:"allowed_regexp_prefixes,omitempty"` } func (r RepoCfg) Validate() error { @@ -87,5 +88,6 @@ func (r RepoCfg) ToValid() valid.RepoCfg { ParallelPlan: parallelPlan, ParallelPolicyCheck: parallelPlan, DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge, + AllowedRegexpPrefixes: r.AllowedRegexpPrefixes, } } diff --git a/server/events/yaml/raw/repo_cfg_test.go b/server/events/yaml/raw/repo_cfg_test.go index 430b8c61b9..aa2deb8c95 100644 --- a/server/events/yaml/raw/repo_cfg_test.go +++ b/server/events/yaml/raw/repo_cfg_test.go @@ -144,7 +144,10 @@ workflows: policy_check: steps: [] apply: - steps: []`, + steps: [] +allowed_regexp_prefixes: +- dev/ +- staging/`, exp: raw.RepoCfg{ Version: Int(3), Automerge: Bool(true), @@ -176,6 +179,7 @@ workflows: }, }, }, + AllowedRegexpPrefixes: []string{"dev/", "staging/"}, }, }, } diff --git a/server/events/yaml/valid/repo_cfg.go b/server/events/yaml/valid/repo_cfg.go index b107c06b41..c73b978e35 100644 --- a/server/events/yaml/valid/repo_cfg.go +++ b/server/events/yaml/valid/repo_cfg.go @@ -22,6 +22,7 @@ type RepoCfg struct { ParallelPlan bool ParallelPolicyCheck bool DeleteSourceBranchOnMerge *bool + AllowedRegexpPrefixes []string } func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project { @@ -57,17 +58,31 @@ func (r RepoCfg) FindProjectByName(name string) *Project { // 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 isRegexAllowed(name, r.AllowedRegexpPrefixes) { + sanitizedName := "^" + name + "$" + for _, p := range r.Projects { + if p.Name != nil { + if match, _ := regexp.MatchString(sanitizedName, *p.Name); match { + ps = append(ps, p) + } } } } 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 +} + // 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 diff --git a/server/events/yaml/valid/repo_cfg_test.go b/server/events/yaml/valid/repo_cfg_test.go new file mode 100644 index 0000000000..28cee4fc9f --- /dev/null +++ b/server/events/yaml/valid/repo_cfg_test.go @@ -0,0 +1,175 @@ +package valid_test + +import ( + "testing" + + validation "github.com/go-ozzo/ozzo-validation" + version "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/events/yaml/valid" + . "github.com/runatlantis/atlantis/testing" +) + +func TestConfig_FindProjectsByDir(t *testing.T) { + tfVersion, _ := version.NewVersion("v0.11.0") + cases := []struct { + description string + nameRegex string + input valid.RepoCfg + expProjects []valid.Project + }{ + { + description: "Find projects with 'dev' prefix as allowed prefix", + nameRegex: "dev.*", + input: valid.RepoCfg{ + Version: 3, + Projects: []valid.Project{ + { + Dir: ".", + Name: String("dev_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Name: "myworkflow", + Apply: valid.DefaultApplyStage, + Plan: valid.DefaultPlanStage, + PolicyCheck: valid.DefaultPolicyCheckStage, + }, + }, + AllowedRegexpPrefixes: []string{"dev", "staging"}, + }, + expProjects: []valid.Project{ + { + Dir: ".", + Name: String("dev_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + }, + }, + { + description: "Only find projects with allowed prefix", + nameRegex: ".*", + input: valid.RepoCfg{ + Version: 3, + Projects: []valid.Project{ + { + Dir: ".", + Name: String("dev_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + { + Dir: ".", + Name: String("staging_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Name: "myworkflow", + Apply: valid.DefaultApplyStage, + Plan: valid.DefaultPlanStage, + PolicyCheck: valid.DefaultPolicyCheckStage, + }, + }, + AllowedRegexpPrefixes: []string{"dev", "staging"}, + }, + expProjects: nil, + }, + { + description: "Find all projects without restrictions of allowed prefix", + nameRegex: ".*", + input: valid.RepoCfg{ + Version: 3, + Projects: []valid.Project{ + { + Dir: ".", + Name: String("dev_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + { + Dir: ".", + Name: String("staging_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Name: "myworkflow", + Apply: valid.DefaultApplyStage, + Plan: valid.DefaultPlanStage, + PolicyCheck: valid.DefaultPolicyCheckStage, + }, + }, + AllowedRegexpPrefixes: nil, + }, + expProjects: []valid.Project{ + { + Dir: ".", + Name: String("dev_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + { + Dir: ".", + Name: String("staging_terragrunt_myproject"), + Workspace: "myworkspace", + TerraformVersion: tfVersion, + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf*", "**/terragrunt.hcl"}, + Enabled: false, + }, + ApplyRequirements: []string{"approved"}, + }, + }, + }, + } + validation.ErrorTag = "yaml" + for _, c := range cases { + t.Run(c.description, func(t *testing.T) { + projects := c.input.FindProjectsByName(c.nameRegex) + Equals(t, c.expProjects, projects) + }) + } +}