From 1f15562167a3fd17db575f3ad3816efbe204d378 Mon Sep 17 00:00:00 2001 From: anryko Date: Mon, 13 May 2024 19:08:43 +0200 Subject: [PATCH] feat: add silence_pr_comments on plan and apply --- .../docs/repo-level-atlantis-yaml.md | 3 + .../docs/server-side-repo-config.md | 1 + server/core/config/parser_validator_test.go | 10 ++- server/core/config/raw/global_cfg.go | 25 ++++++- server/core/config/raw/project.go | 5 ++ server/core/config/raw/repo_cfg.go | 2 + server/core/config/valid/global_cfg.go | 65 +++++++++++++++++-- server/core/config/valid/global_cfg_test.go | 2 +- server/core/config/valid/repo_cfg.go | 2 + server/events/apply_command_runner.go | 1 + server/events/command/project_context.go | 1 + server/events/command/project_result.go | 1 + server/events/plan_command_runner.go | 1 + server/events/project_command_builder.go | 12 ++-- .../events/project_command_context_builder.go | 1 + server/events/project_command_runner.go | 30 +++++---- server/events/pull_updater.go | 20 +++++- 17 files changed, 147 insertions(+), 35 deletions(-) diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md index 0c9af490d6..11feb31224 100644 --- a/runatlantis.io/docs/repo-level-atlantis-yaml.md +++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md @@ -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 @@ -433,6 +434,7 @@ terraform_version: 0.11.0 plan_requirements: ["approved"] apply_requirements: ["approved"] import_requirements: ["approved"] +silence_pr_comments: ["apply"] workflow: myworkflow ``` @@ -452,6 +454,7 @@ workflow: myworkflow | plan_requirements
*(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
*(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
*(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
*(restricted)* | string | none | no | A custom workflow. If not specified, Atlantis will use its default workflow. | ::: tip diff --git a/runatlantis.io/docs/server-side-repo-config.md b/runatlantis.io/docs/server-side-repo-config.md index f7cd73595c..16899ac9a3 100644 --- a/runatlantis.io/docs/server-side-repo-config.md +++ b/runatlantis.io/docs/server-side-repo-config.md @@ -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 diff --git a/server/core/config/parser_validator_test.go b/server/core/config/parser_validator_test.go index 24b66e4db7..815adbd338 100644 --- a/server/core/config/parser_validator_test.go +++ b/server/core/config/parser_validator_test.go @@ -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: @@ -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`, diff --git a/server/core/config/raw/global_cfg.go b/server/core/config/raw/global_cfg.go index 445332ec39..0c9b2c351d 100644 --- a/server/core/config/raw/global_cfg.go +++ b/server/core/config/raw/global_cfg.go @@ -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. @@ -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 { @@ -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 } @@ -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 @@ -365,5 +385,6 @@ OuterGlobalImportReqs: PolicyCheck: r.PolicyCheck, CustomPolicyCheck: r.CustomPolicyCheck, AutoDiscover: autoDiscover, + SilencePRComments: r.SilencePRComments, } } diff --git a/server/core/config/raw/project.go b/server/core/config/raw/project.go index a20aad10af..fe0e656a8c 100644 --- a/server/core/config/raw/project.go +++ b/server/core/config/raw/project.go @@ -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 { @@ -156,6 +157,10 @@ func (p Project) ToValid() valid.Project { v.CustomPolicyCheck = p.CustomPolicyCheck } + if p.SilencePRComments != nil { + v.SilencePRComments = p.SilencePRComments + } + return v } diff --git a/server/core/config/raw/repo_cfg.go b/server/core/config/raw/repo_cfg.go index e7b4b273ab..9aa18c7733 100644 --- a/server/core/config/raw/repo_cfg.go +++ b/server/core/config/raw/repo_cfg.go @@ -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 { @@ -96,5 +97,6 @@ func (r RepoCfg) ToValid() valid.RepoCfg { EmojiReaction: emojiReaction, AbortOnExcecutionOrderFail: abortOnExcecutionOrderFail, RepoLocks: repoLocks, + SilencePRComments: r.SilencePRComments, } } diff --git a/server/core/config/valid/global_cfg.go b/server/core/config/valid/global_cfg.go index 72bfa60432..ac41b3865e 100644 --- a/server/core/config/valid/global_cfg.go +++ b/server/core/config/valid/global_cfg.go @@ -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" @@ -85,6 +88,7 @@ type Repo struct { PolicyCheck *bool CustomPolicyCheck *bool AutoDiscover *AutoDiscover + SilencePRComments []string } type MergedProjectCfg struct { @@ -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. @@ -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 } @@ -237,6 +243,7 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg { PolicyCheck: &policyCheck, CustomPolicyCheck: &customPolicyCheck, AutoDiscover: &autoDiscover, + SilencePRComments: silencePRComments, }, }, Workflows: map[string]Workflow{ @@ -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 { @@ -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, @@ -391,6 +415,7 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro RepoLocks: repoLocks, PolicyCheck: policyCheck, CustomPolicyCheck: customPolicyCheck, + SilencePRComments: silencePRComments, } } @@ -398,7 +423,7 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro // 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, @@ -414,6 +439,7 @@ func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repo RepoLocks: repoLocks, PolicyCheck: policyCheck, CustomPolicyCheck: customPolicyCheck, + SilencePRComments: silencePRComments, } } @@ -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. @@ -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" @@ -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 { @@ -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 + } } } } diff --git a/server/core/config/valid/global_cfg_test.go b/server/core/config/valid/global_cfg_test.go index 6c1b94ded2..129b41d462 100644 --- a/server/core/config/valid/global_cfg_test.go +++ b/server/core/config/valid/global_cfg_test.go @@ -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") diff --git a/server/core/config/valid/repo_cfg.go b/server/core/config/valid/repo_cfg.go index 7010d1c95d..e5a8378bd7 100644 --- a/server/core/config/valid/repo_cfg.go +++ b/server/core/config/valid/repo_cfg.go @@ -29,6 +29,7 @@ type RepoCfg struct { EmojiReaction string AllowedRegexpPrefixes []string AbortOnExcecutionOrderFail bool + SilencePRComments []string } func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project { @@ -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 diff --git a/server/events/apply_command_runner.go b/server/events/apply_command_runner.go index 2a40c454d9..ee6bf8ab1f 100644 --- a/server/events/apply_command_runner.go +++ b/server/events/apply_command_runner.go @@ -60,6 +60,7 @@ type ApplyCommandRunner struct { // SilenceVCSStatusNoPlans is whether any plan should set commit status if no projects // are found silenceVCSStatusNoProjects bool + SilencePRComments []string } func (a *ApplyCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) { diff --git a/server/events/command/project_context.go b/server/events/command/project_context.go index dc4c6dffa4..6e221ff4d1 100644 --- a/server/events/command/project_context.go +++ b/server/events/command/project_context.go @@ -125,6 +125,7 @@ type ProjectContext struct { AbortOnExcecutionOrderFail bool // Allows custom policy check tools outside of Conftest to run in checks CustomPolicyCheck bool + SilencePRComments []string } // SetProjectScopeTags adds ProjectContext tags to a new returned scope. diff --git a/server/events/command/project_result.go b/server/events/command/project_result.go index 0d59c4e9ab..8f72f1d168 100644 --- a/server/events/command/project_result.go +++ b/server/events/command/project_result.go @@ -19,6 +19,7 @@ type ProjectResult struct { ImportSuccess *models.ImportSuccess StateRmSuccess *models.StateRmSuccess ProjectName string + SilencePRComments []string } // CommitStatus returns the vcs commit status of this project result. diff --git a/server/events/plan_command_runner.go b/server/events/plan_command_runner.go index b8657f5a85..c2b6b7a107 100644 --- a/server/events/plan_command_runner.go +++ b/server/events/plan_command_runner.go @@ -76,6 +76,7 @@ type PlanCommandRunner struct { // a plan. DiscardApprovalOnPlan bool pullReqStatusFetcher vcs.PullReqStatusFetcher + SilencePRComments []string } func (p *PlanCommandRunner) runAutoplan(ctx *command.Context) { diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index d40ae25893..538c71a73f 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -276,8 +276,7 @@ func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, c } ctx.Log.Debug("Building plan command for specific project with directory: '%v', workspace: '%v', project: '%v'", cmd.RepoRelDir, cmd.Workspace, cmd.ProjectName) - pcc, err := p.buildProjectPlanCommand(ctx, cmd) - return pcc, err + return p.buildProjectPlanCommand(ctx, cmd) } // See ProjectCommandBuilder.BuildApplyCommands. @@ -285,24 +284,21 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *command.Context, if !cmd.IsForSpecificProject() { return p.buildAllProjectCommandsByPlan(ctx, cmd) } - pac, err := p.buildProjectCommand(ctx, cmd) - return pac, err + return p.buildProjectCommand(ctx, cmd) } func (p *DefaultProjectCommandBuilder) BuildApprovePoliciesCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { if !cmd.IsForSpecificProject() { return p.buildAllProjectCommandsByPlan(ctx, cmd) } - pac, err := p.buildProjectCommand(ctx, cmd) - return pac, err + return p.buildProjectCommand(ctx, cmd) } func (p *DefaultProjectCommandBuilder) BuildVersionCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { if !cmd.IsForSpecificProject() { return p.buildAllProjectCommandsByPlan(ctx, cmd) } - pac, err := p.buildProjectCommand(ctx, cmd) - return pac, err + return p.buildProjectCommand(ctx, cmd) } func (p *DefaultProjectCommandBuilder) BuildImportCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { diff --git a/server/events/project_command_context_builder.go b/server/events/project_command_context_builder.go index ea17e02e07..6a08b16471 100644 --- a/server/events/project_command_context_builder.go +++ b/server/events/project_command_context_builder.go @@ -306,6 +306,7 @@ func newProjectCommandContext(ctx *command.Context, JobID: uuid.New().String(), ExecutionOrderGroup: projCfg.ExecutionOrderGroup, AbortOnExcecutionOrderFail: abortOnExcecutionOrderFail, + SilencePRComments: projCfg.SilencePRComments, } } diff --git a/server/events/project_command_runner.go b/server/events/project_command_runner.go index cd1b2e0d15..5410dee8cb 100644 --- a/server/events/project_command_runner.go +++ b/server/events/project_command_runner.go @@ -225,13 +225,14 @@ type DefaultProjectCommandRunner struct { func (p *DefaultProjectCommandRunner) Plan(ctx command.ProjectContext) command.ProjectResult { planSuccess, failure, err := p.doPlan(ctx) return command.ProjectResult{ - Command: command.Plan, - PlanSuccess: planSuccess, - Error: err, - Failure: failure, - RepoRelDir: ctx.RepoRelDir, - Workspace: ctx.Workspace, - ProjectName: ctx.ProjectName, + Command: command.Plan, + PlanSuccess: planSuccess, + Error: err, + Failure: failure, + RepoRelDir: ctx.RepoRelDir, + Workspace: ctx.Workspace, + ProjectName: ctx.ProjectName, + SilencePRComments: ctx.SilencePRComments, } } @@ -253,13 +254,14 @@ func (p *DefaultProjectCommandRunner) PolicyCheck(ctx command.ProjectContext) co func (p *DefaultProjectCommandRunner) Apply(ctx command.ProjectContext) command.ProjectResult { applyOut, failure, err := p.doApply(ctx) return command.ProjectResult{ - Command: command.Apply, - Failure: failure, - Error: err, - ApplySuccess: applyOut, - RepoRelDir: ctx.RepoRelDir, - Workspace: ctx.Workspace, - ProjectName: ctx.ProjectName, + Command: command.Apply, + Failure: failure, + Error: err, + ApplySuccess: applyOut, + RepoRelDir: ctx.RepoRelDir, + Workspace: ctx.Workspace, + ProjectName: ctx.ProjectName, + SilencePRComments: ctx.SilencePRComments, } } diff --git a/server/events/pull_updater.go b/server/events/pull_updater.go index 2fd2b99a16..d85bd84f9d 100644 --- a/server/events/pull_updater.go +++ b/server/events/pull_updater.go @@ -3,6 +3,7 @@ package events import ( "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/vcs" + "github.com/runatlantis/atlantis/server/utils" ) type PullUpdater struct { @@ -23,12 +24,29 @@ func (c *PullUpdater) updatePull(ctx *command.Context, cmd PullCommand, res comm // clutter in a pull/merge request. This will not delete the comment, since the // comment trail may be useful in auditing or backtracing problems. if c.HidePrevPlanComments { - ctx.Log.Debug("Hiding previous plan comments for command: '%v', directory: '%v'", cmd.CommandName().TitleString(), cmd.Dir()) + ctx.Log.Debug("hiding previous plan comments for command: '%v', directory: '%v'", cmd.CommandName().TitleString(), cmd.Dir()) if err := c.VCSClient.HidePrevCommandComments(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull.Num, cmd.CommandName().TitleString(), cmd.Dir()); err != nil { ctx.Log.Err("unable to hide old comments: %s", err) } } + if len(res.ProjectResults) > 0 { + var commentOnProjects []command.ProjectResult + for _, result := range res.ProjectResults { + if utils.SlicesContains(result.SilencePRComments, cmd.CommandName().String()) { + ctx.Log.Debug("silenced command '%s' comment for project '%s'", cmd.CommandName().String(), result.ProjectName) + continue + } + commentOnProjects = append(commentOnProjects, result) + } + + if len(commentOnProjects) == 0 { + return + } + + res.ProjectResults = commentOnProjects + } + comment := c.MarkdownRenderer.Render(ctx, res, cmd) if err := c.VCSClient.CreateComment(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull.Num, comment, cmd.CommandName().String()); err != nil { ctx.Log.Err("unable to comment: %s", err)