diff --git a/action.yml b/action.yml index bcc0677d8..df46e0224 100644 --- a/action.yml +++ b/action.yml @@ -86,6 +86,10 @@ inputs: description: Setup tfenv required: false default: 'false' + post-plans-as-one-comment: + description: Post plans as one comment + required: false + default: 'false' outputs: output: value: ${{ steps.digger.outputs.output }} @@ -186,6 +190,7 @@ runs: POLICY_CHECK_ENABLED: ${{ inputs.policy-check-enabled == 'true' }} DIGGER_CLOUD_TOKEN: ${{ inputs.digger-cloud-token }} DIGGER_CLOUD_HOSTNAME: ${{ inputs.digger-cloud-hostname }} + ACCUMULATE_PLANS: ${{ inputs.post-plans-as-one-comment == 'true' }} run: | cd ${{ github.action_path }} go build -o digger ./cmd/digger @@ -203,6 +208,7 @@ runs: POLICY_CHECK_ENABLED: ${{ inputs.policy-check-enabled == 'true' }} DIGGER_CLOUD_TOKEN: ${{ inputs.digger-cloud-token }} DIGGER_CLOUD_HOSTNAME: ${{ inputs.digger-cloud-hostname }} + ACCUMULATE_PLANS: ${{ inputs.post-plans-as-one-comment == 'true' }} id: digger shell: bash run: | diff --git a/pkg/core/execution/execution.go b/pkg/core/execution/execution.go index d32087a60..6cdc4cb66 100644 --- a/pkg/core/execution/execution.go +++ b/pkg/core/execution/execution.go @@ -43,10 +43,11 @@ func (d DiggerExecutor) storedPlanFilePath() string { return path.Join(d.ProjectNamespace, d.planFileName()) } -func (d DiggerExecutor) Plan() (bool, error) { +func (d DiggerExecutor) Plan() (bool, string, error) { + plan := "" locked, err := d.ProjectLock.Lock() if err != nil { - return false, fmt.Errorf("error locking project: %v", err) + return false, "", fmt.Errorf("error locking project: %v", err) } log.Printf("Lock result: %t\n", locked) if locked { @@ -68,7 +69,7 @@ func (d DiggerExecutor) Plan() (bool, error) { if step.Action == "init" { _, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars) if err != nil { - return false, fmt.Errorf("error running init: %v", err) + return false, "", fmt.Errorf("error running init: %v", err) } } if step.Action == "plan" { @@ -76,29 +77,27 @@ func (d DiggerExecutor) Plan() (bool, error) { planArgs = append(planArgs, step.ExtraArgs...) isNonEmptyPlan, stdout, stderr, err := d.TerraformExecutor.Plan(planArgs, d.CommandEnvVars) if err != nil { - return false, fmt.Errorf("error executing plan: %v", err) + return false, "", fmt.Errorf("error executing plan: %v", err) } if d.PlanStorage != nil { planExists, err := d.PlanStorage.PlanExists(d.storedPlanFilePath()) if err != nil { - return false, fmt.Errorf("error checking if plan exists: %v", err) + return false, "", fmt.Errorf("error checking if plan exists: %v", err) } if planExists { err = d.PlanStorage.DeleteStoredPlan(d.storedPlanFilePath()) if err != nil { - return false, fmt.Errorf("error deleting plan: %v", err) + return false, "", fmt.Errorf("error deleting plan: %v", err) } } err = d.PlanStorage.StorePlan(d.localPlanFilePath(), d.storedPlanFilePath()) if err != nil { - return false, fmt.Errorf("error storing plan: %v", err) + return false, "", fmt.Errorf("error storing plan: %v", err) } } - plan := cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr) - comment := utils.GetTerraformOutputAsCollapsibleComment("Plan for **"+d.ProjectLock.LockId()+"**", plan) - err = d.Reporter.Report(comment) + plan = cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr) if err != nil { fmt.Printf("error publishing comment: %v", err) } @@ -112,13 +111,13 @@ func (d DiggerExecutor) Plan() (bool, error) { log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId()) _, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands) if err != nil { - return false, fmt.Errorf("error running command: %v", err) + return false, "", fmt.Errorf("error running command: %v", err) } } } - return true, nil + return true, plan, nil } - return false, nil + return false, plan, nil } func (d DiggerExecutor) Apply() (bool, error) { diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go index 2e344595f..45bf271af 100644 --- a/pkg/digger/digger.go +++ b/pkg/digger/digger.go @@ -10,6 +10,7 @@ import ( "digger/pkg/core/runners" "digger/pkg/core/storage" "digger/pkg/core/terraform" + "digger/pkg/core/utils" "digger/pkg/locking" "digger/pkg/usage" "errors" @@ -17,6 +18,7 @@ import ( "log" "os" "path" + "strings" "time" ) @@ -56,8 +58,22 @@ func DetectCI() CIName { } -func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNamespace string, requestedBy string, eventName string, prNumber int, ciService ci.CIService, lock core_locking.Lock, reporter reporting.Reporter, planStorage storage.PlanStorage, policyChecker policy.Checker, workingDir string) (bool, bool, error) { +func RunCommandsPerProject( + commandsPerProject []models.ProjectCommand, + projectNamespace string, + requestedBy string, + eventName string, + prNumber int, + ciService ci.CIService, + lock core_locking.Lock, + reporter reporting.Reporter, + planStorage storage.PlanStorage, + policyChecker policy.Checker, + workingDir string, +) (bool, bool, error) { + accumulatePlans := os.Getenv("ACCUMULATE_PLANS") == "true" appliesPerProject := make(map[string]bool) + plansToPublish := make([]string, 0) for _, projectCommands := range commandsPerProject { for _, command := range projectCommands.Commands { policyInput := map[string]interface{}{"user": requestedBy, "action": command} @@ -112,12 +128,21 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa case "digger plan": usage.SendUsageRecord(requestedBy, eventName, "plan") ciService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/plan") - planPerformed, err := diggerExecutor.Plan() + planPerformed, plan, err := diggerExecutor.Plan() if err != nil { log.Printf("Failed to run digger plan command. %v", err) ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/plan") return false, false, fmt.Errorf("failed to run digger plan command. %v", err) } else if planPerformed { + comment := utils.GetTerraformOutputAsCollapsibleComment("Plan for **"+projectLock.LockId()+"**", plan) + if accumulatePlans { + plansToPublish = append(plansToPublish, comment) + } else { + err = reporter.Report(comment) + if err != nil { + log.Printf("Failed to report plan. %v", err) + } + } ciService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/plan") } case "digger apply": @@ -164,6 +189,13 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa } } + if len(plansToPublish) > 0 { + err := reporter.Report(strings.Join(plansToPublish, "\n")) + if err != nil { + log.Printf("Failed to report plans. %v", err) + } + } + allAppliesSuccess := true for _, success := range appliesPerProject { if !success { diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go index 1421e76bf..daf597c49 100644 --- a/pkg/digger/digger_test.go +++ b/pkg/digger/digger_test.go @@ -241,7 +241,7 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) { commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage) - assert.Equal(t, []string{"Lock ", "Init ", "Plan -out #.tfplan", "PlanExists #.tfplan", "StorePlan #.tfplan", "LockId ", "PublishComment 1
\n Plan for ****\n\n ```terraform\n\n ```\n
", "LockId ", "Run echo"}, commandStrings) + assert.Equal(t, []string{"Lock ", "Init ", "Plan -out #.tfplan", "PlanExists #.tfplan", "StorePlan #.tfplan", "LockId ", "Run echo"}, commandStrings) } func allCommandsInOrderWithParams(terraformExecutor *MockTerraformExecutor, commandRunner *MockCommandRunner, prManager *MockPRManager, lock *MockProjectLock, planStorage *MockPlanStorage) []string {