Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add silence_pr_comments on plan and apply #4543

Merged
merged 4 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions runatlantis.io/docs/repo-level-atlantis-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ projects:
plan_requirements: [mergeable, approved, undiverged]
apply_requirements: [mergeable, approved, undiverged]
import_requirements: [mergeable, approved, undiverged]
silence_pr_comments: ["apply"]
execution_order_group: 1
depends_on:
- project-1
Expand Down Expand Up @@ -433,6 +434,7 @@ terraform_version: 0.11.0
plan_requirements: ["approved"]
apply_requirements: ["approved"]
import_requirements: ["approved"]
silence_pr_comments: ["apply"]
workflow: myworkflow
```

Expand All @@ -452,6 +454,7 @@ workflow: myworkflow
| plan_requirements<br />*(restricted)* | array\[string\] | none | no | Requirements that must be satisfied before `atlantis plan` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.md) for more details. |
| apply_requirements<br />*(restricted)* | array\[string\] | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.md) for more details. |
| import_requirements<br />*(restricted)* | array\[string\] | none | no | Requirements that must be satisfied before `atlantis import` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.md) for more details. |
| silence_pr_comments | array\[string\] | none | no | Silence PR comments from defined stages while preserving PR status checks. Supported values are: `plan`, `apply`. |
| workflow <br />*(restricted)* | string | none | no | A custom workflow. If not specified, Atlantis will use its default workflow. |

::: tip
Expand Down
1 change: 1 addition & 0 deletions runatlantis.io/docs/server-side-repo-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ If you set a workflow with the key `default`, it will override this.
| policy_check | bool | false | no | Whether or not to run policy checks on this repository. |
| custom_policy_check | bool | false | no | Whether or not to enable custom policy check tools outside of Conftest on this repository. |
| autodiscover | AutoDiscover | none | no | Auto discover settings for this repo |
| silence_pr_comments | []string | none | no | Silence PR comments from defined stages while preserving PR status checks. Useful in large environments with many Atlantis instances and/or projects, when the comments are too big and too many, therefore it is preferable to rely solely on PR status checks. Supported values are: `plan`, `apply`. |

:::tip Notes

Expand Down
10 changes: 8 additions & 2 deletions server/core/config/parser_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ func TestParseGlobalCfg(t *testing.T) {
input: `repos:
- id: /.*/
allowed_overrides: [invalid]`,
expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"plan_requirements\", \"apply_requirements\", \"import_requirements\", \"workflow\", \"delete_source_branch_on_merge\", \"repo_locking\", \"repo_locks\", \"policy_check\", and \"custom_policy_check\" are supported.).).",
expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"plan_requirements\", \"apply_requirements\", \"import_requirements\", \"workflow\", \"delete_source_branch_on_merge\", \"repo_locking\", \"repo_locks\", \"policy_check\", \"custom_policy_check\", and \"silence_pr_comments\" are supported.).).",
},
"invalid plan_requirement": {
input: `repos:
Expand All @@ -1306,8 +1306,14 @@ func TestParseGlobalCfg(t *testing.T) {
import_requirements: [invalid]`,
expErr: "repos: (0: (import_requirements: \"invalid\" is not a valid import_requirement, only \"approved\", \"mergeable\" and \"undiverged\" are supported.).).",
},
"invalid silence_pr_comments": {
input: `repos:
- id: /.*/
silence_pr_comments: [invalid]`,
expErr: "server-side repo config 'silence_pr_comments' key value of 'invalid' is not supported, supported values are [plan, apply]",
},
"disable autodiscover": {
input: `repos:
input: `repos:
- id: /.*/
autodiscover:
mode: disabled`,
Expand Down
25 changes: 23 additions & 2 deletions server/core/config/raw/global_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
validation "github.com/go-ozzo/ozzo-validation"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/core/config/valid"
"github.com/runatlantis/atlantis/server/utils"
)

// GlobalCfg is the raw schema for server-side repo config.
Expand Down Expand Up @@ -38,6 +39,7 @@ type Repo struct {
PolicyCheck *bool `yaml:"policy_check,omitempty" json:"policy_check,omitempty"`
CustomPolicyCheck *bool `yaml:"custom_policy_check,omitempty" json:"custom_policy_check,omitempty"`
AutoDiscover *AutoDiscover `yaml:"autodiscover,omitempty" json:"autodiscover,omitempty"`
SilencePRComments []string `yaml:"silence_pr_comments,omitempty" json:"silence_pr_comments,omitempty"`
}

func (g GlobalCfg) Validate() error {
Expand Down Expand Up @@ -94,6 +96,24 @@ func (g GlobalCfg) Validate() error {
}
}
}

// Validate supported SilencePRComments values.
for _, repo := range g.Repos {
if repo.SilencePRComments == nil {
continue
}
for _, silenceStage := range repo.SilencePRComments {
if !utils.SlicesContains(valid.AllowedSilencePRComments, silenceStage) {
return fmt.Errorf(
"server-side repo config '%s' key value of '%s' is not supported, supported values are [%s]",
valid.SilencePRCommentsKey,
silenceStage,
strings.Join(valid.AllowedSilencePRComments, ", "),
)
}
}
}

return nil
}

Expand Down Expand Up @@ -195,8 +215,8 @@ func (r Repo) Validate() error {
overridesValid := func(value interface{}) error {
overrides := value.([]string)
for _, o := range overrides {
if o != valid.PlanRequirementsKey && o != valid.ApplyRequirementsKey && o != valid.ImportRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey && o != valid.RepoLockingKey && o != valid.RepoLocksKey && o != valid.PolicyCheckKey && o != valid.CustomPolicyCheckKey {
return fmt.Errorf("%q is not a valid override, only %q, %q, %q, %q, %q, %q, %q, %q, and %q are supported", o, valid.PlanRequirementsKey, valid.ApplyRequirementsKey, valid.ImportRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey, valid.RepoLockingKey, valid.RepoLocksKey, valid.PolicyCheckKey, valid.CustomPolicyCheckKey)
if o != valid.PlanRequirementsKey && o != valid.ApplyRequirementsKey && o != valid.ImportRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey && o != valid.RepoLockingKey && o != valid.RepoLocksKey && o != valid.PolicyCheckKey && o != valid.CustomPolicyCheckKey && o != valid.SilencePRCommentsKey {
return fmt.Errorf("%q is not a valid override, only %q, %q, %q, %q, %q, %q, %q, %q, %q, and %q are supported", o, valid.PlanRequirementsKey, valid.ApplyRequirementsKey, valid.ImportRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey, valid.RepoLockingKey, valid.RepoLocksKey, valid.PolicyCheckKey, valid.CustomPolicyCheckKey, valid.SilencePRCommentsKey)
}
}
return nil
Expand Down Expand Up @@ -365,5 +385,6 @@ OuterGlobalImportReqs:
PolicyCheck: r.PolicyCheck,
CustomPolicyCheck: r.CustomPolicyCheck,
AutoDiscover: autoDiscover,
SilencePRComments: r.SilencePRComments,
}
}
5 changes: 5 additions & 0 deletions server/core/config/raw/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Project struct {
ExecutionOrderGroup *int `yaml:"execution_order_group,omitempty"`
PolicyCheck *bool `yaml:"policy_check,omitempty"`
CustomPolicyCheck *bool `yaml:"custom_policy_check,omitempty"`
SilencePRComments []string `yaml:"silence_pr_comments,omitempty"`
}

func (p Project) Validate() error {
Expand Down Expand Up @@ -156,6 +157,10 @@ func (p Project) ToValid() valid.Project {
v.CustomPolicyCheck = p.CustomPolicyCheck
}

if p.SilencePRComments != nil {
v.SilencePRComments = p.SilencePRComments
}

return v
}

Expand Down
2 changes: 2 additions & 0 deletions server/core/config/raw/repo_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type RepoCfg struct {
AllowedRegexpPrefixes []string `yaml:"allowed_regexp_prefixes,omitempty"`
AbortOnExcecutionOrderFail *bool `yaml:"abort_on_execution_order_fail,omitempty"`
RepoLocks *RepoLocks `yaml:"repo_locks,omitempty"`
SilencePRComments []string `yaml:"silence_pr_comments,omitempty"`
}

func (r RepoCfg) Validate() error {
Expand Down Expand Up @@ -96,5 +97,6 @@ func (r RepoCfg) ToValid() valid.RepoCfg {
EmojiReaction: emojiReaction,
AbortOnExcecutionOrderFail: abortOnExcecutionOrderFail,
RepoLocks: repoLocks,
SilencePRComments: r.SilencePRComments,
}
}
65 changes: 58 additions & 7 deletions server/core/config/valid/global_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const RepoLocksKey = "repo_locks"
const PolicyCheckKey = "policy_check"
const CustomPolicyCheckKey = "custom_policy_check"
const AutoDiscoverKey = "autodiscover"
const SilencePRCommentsKey = "silence_pr_comments"

var AllowedSilencePRComments = []string{"plan", "apply"}

// DefaultAtlantisFile is the default name of the config file for each repo.
const DefaultAtlantisFile = "atlantis.yaml"
Expand Down Expand Up @@ -85,6 +88,7 @@ type Repo struct {
PolicyCheck *bool
CustomPolicyCheck *bool
AutoDiscover *AutoDiscover
SilencePRComments []string
}

type MergedProjectCfg struct {
Expand All @@ -107,6 +111,7 @@ type MergedProjectCfg struct {
RepoLocks RepoLocks
PolicyCheck bool
CustomPolicyCheck bool
SilencePRComments []string
}

// WorkflowHook is a map of custom run commands to run before or after workflows.
Expand Down Expand Up @@ -212,8 +217,9 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg {
repoLocks := DefaultRepoLocks
customPolicyCheck := false
autoDiscover := AutoDiscover{Mode: AutoDiscoverAutoMode}
var silencePRComments []string
if args.AllowAllRepoSettings {
allowedOverrides = []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, RepoLocksKey, PolicyCheckKey}
allowedOverrides = []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, RepoLocksKey, PolicyCheckKey, SilencePRCommentsKey}
allowCustomWorkflows = true
}

Expand All @@ -237,6 +243,7 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg {
PolicyCheck: &policyCheck,
CustomPolicyCheck: &customPolicyCheck,
AutoDiscover: &autoDiscover,
SilencePRComments: silencePRComments,
},
},
Workflows: map[string]Workflow{
Expand Down Expand Up @@ -273,7 +280,7 @@ func (r Repo) IDString() string {
// final config. It assumes that all configs have been validated.
func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, proj Project, rCfg RepoCfg) MergedProjectCfg {
log.Debug("MergeProjectCfg started")
planReqs, applyReqs, importReqs, workflow, allowedOverrides, allowCustomWorkflows, deleteSourceBranchOnMerge, repoLocks, policyCheck, customPolicyCheck, _ := g.getMatchingCfg(log, repoID)
planReqs, applyReqs, importReqs, workflow, allowedOverrides, allowCustomWorkflows, deleteSourceBranchOnMerge, repoLocks, policyCheck, customPolicyCheck, _, silencePRComments := g.getMatchingCfg(log, repoID)
// If repos are allowed to override certain keys then override them.
for _, key := range allowedOverrides {
switch key {
Expand Down Expand Up @@ -366,12 +373,29 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro
log.Debug("overriding server-defined %s with repo settings: [%t]", CustomPolicyCheckKey, *proj.CustomPolicyCheck)
customPolicyCheck = *proj.CustomPolicyCheck
}
case SilencePRCommentsKey:
if proj.SilencePRComments != nil {
log.Debug("overriding repo-root-defined %s with repo settings: [%t]", SilencePRCommentsKey, strings.Join(proj.SilencePRComments, ","))
silencePRComments = proj.SilencePRComments
} else if rCfg.SilencePRComments != nil {
log.Debug("overriding server-defined %s with repo settings: [%s]", SilencePRCommentsKey, strings.Join(rCfg.SilencePRComments, ","))
silencePRComments = rCfg.SilencePRComments
}
}
log.Debug("MergeProjectCfg completed")
}

log.Debug("final settings: %s: [%s], %s: [%s], %s: [%s], %s: %s",
PlanRequirementsKey, strings.Join(planReqs, ","), ApplyRequirementsKey, strings.Join(applyReqs, ","), ImportRequirementsKey, strings.Join(importReqs, ","), WorkflowKey, workflow.Name)
log.Debug("final settings: %s: [%s], %s: [%s], %s: [%s], %s: %s, %s: %t, %s: %s, %s: %t, %s: %t, %s: [%s]",
PlanRequirementsKey, strings.Join(planReqs, ","),
ApplyRequirementsKey, strings.Join(applyReqs, ","),
ImportRequirementsKey, strings.Join(importReqs, ","),
WorkflowKey, workflow.Name,
DeleteSourceBranchOnMergeKey, deleteSourceBranchOnMerge,
RepoLockingKey, repoLocks.Mode,
PolicyCheckKey, policyCheck,
CustomPolicyCheckKey, policyCheck,
SilencePRCommentsKey, strings.Join(silencePRComments, ","),
)

return MergedProjectCfg{
PlanRequirements: planReqs,
Expand All @@ -391,14 +415,15 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro
RepoLocks: repoLocks,
PolicyCheck: policyCheck,
CustomPolicyCheck: customPolicyCheck,
SilencePRComments: silencePRComments,
}
}

// DefaultProjCfg returns the default project config for all projects under the
// repo with id repoID. It is used when there is no repo config.
func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repoRelDir string, workspace string) MergedProjectCfg {
log.Debug("building config based on server-side config")
planReqs, applyReqs, importReqs, workflow, _, _, deleteSourceBranchOnMerge, repoLocks, policyCheck, customPolicyCheck, _ := g.getMatchingCfg(log, repoID)
planReqs, applyReqs, importReqs, workflow, _, _, deleteSourceBranchOnMerge, repoLocks, policyCheck, customPolicyCheck, _, silencePRComments := g.getMatchingCfg(log, repoID)
return MergedProjectCfg{
PlanRequirements: planReqs,
ApplyRequirements: applyReqs,
Expand All @@ -414,6 +439,7 @@ func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repo
RepoLocks: repoLocks,
PolicyCheck: policyCheck,
CustomPolicyCheck: customPolicyCheck,
SilencePRComments: silencePRComments,
}
}

Expand Down Expand Up @@ -474,6 +500,26 @@ func (g GlobalCfg) ValidateRepoCfg(rCfg RepoCfg, repoID string) error {
if p.CustomPolicyCheck != nil && !utils.SlicesContains(allowedOverrides, CustomPolicyCheckKey) {
return fmt.Errorf("repo config not allowed to set '%s' key: server-side config needs '%s: [%s]'", CustomPolicyCheckKey, AllowedOverridesKey, CustomPolicyCheckKey)
}
if p.SilencePRComments != nil {
if !utils.SlicesContains(allowedOverrides, SilencePRCommentsKey) {
return fmt.Errorf(
"repo config not allowed to set '%s' key: server-side config needs '%s: [%s]'",
SilencePRCommentsKey,
AllowedOverridesKey,
SilencePRCommentsKey,
)
}
for _, silenceStage := range p.SilencePRComments {
if !utils.SlicesContains(AllowedSilencePRComments, silenceStage) {
return fmt.Errorf(
"repo config '%s' key value of '%s' is not supported, supported values are [%s]",
SilencePRCommentsKey,
silenceStage,
strings.Join(AllowedSilencePRComments, ", "),
)
}
}
}
}

// Check custom workflows.
Expand Down Expand Up @@ -532,7 +578,7 @@ func (g GlobalCfg) ValidateRepoCfg(rCfg RepoCfg, repoID string) error {
}

// getMatchingCfg returns the key settings for repoID.
func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (planReqs []string, applyReqs []string, importReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool, deleteSourceBranchOnMerge bool, repoLocks RepoLocks, policyCheck bool, customPolicyCheck bool, autoDiscover AutoDiscover) {
func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (planReqs []string, applyReqs []string, importReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool, deleteSourceBranchOnMerge bool, repoLocks RepoLocks, policyCheck bool, customPolicyCheck bool, autoDiscover AutoDiscover, silencePRComments []string) {
toLog := make(map[string]string)
traceF := func(repoIdx int, repoID string, key string, val interface{}) string {
from := "default server config"
Expand All @@ -559,7 +605,7 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (pla
repoLocking := true
repoLocks = DefaultRepoLocks

for _, key := range []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, RepoLocksKey, PolicyCheckKey, CustomPolicyCheckKey} {
for _, key := range []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, RepoLocksKey, PolicyCheckKey, CustomPolicyCheckKey, SilencePRCommentsKey} {
for i, repo := range g.Repos {
if repo.IDMatches(repoID) {
switch key {
Expand Down Expand Up @@ -623,6 +669,11 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (pla
toLog[AutoDiscoverKey] = traceF(i, repo.IDString(), AutoDiscoverKey, repo.AutoDiscover.Mode)
autoDiscover = *repo.AutoDiscover
}
case SilencePRCommentsKey:
if repo.SilencePRComments != nil {
toLog[SilencePRCommentsKey] = traceF(i, repo.IDString(), SilencePRCommentsKey, repo.SilencePRComments)
silencePRComments = repo.SilencePRComments
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion server/core/config/valid/global_cfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func TestNewGlobalCfg(t *testing.T) {

if c.allowAllRepoSettings {
exp.Repos[0].AllowCustomWorkflows = Bool(true)
exp.Repos[0].AllowedOverrides = []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge", "repo_locking", "repo_locks", "policy_check"}
exp.Repos[0].AllowedOverrides = []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge", "repo_locking", "repo_locks", "policy_check", "silence_pr_comments"}
}
if c.policyCheckEnabled {
exp.Repos[0].PlanRequirements = append(exp.Repos[0].PlanRequirements, "policies_passed")
Expand Down
2 changes: 2 additions & 0 deletions server/core/config/valid/repo_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type RepoCfg struct {
EmojiReaction string
AllowedRegexpPrefixes []string
AbortOnExcecutionOrderFail bool
SilencePRComments []string
}

func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project {
Expand Down Expand Up @@ -158,6 +159,7 @@ type Project struct {
ExecutionOrderGroup int
PolicyCheck *bool
CustomPolicyCheck *bool
SilencePRComments []string
}

// GetName returns the name of the project or an empty string if there is no
Expand Down
Loading
Loading