Skip to content

Commit

Permalink
feat(stats): pass plan stats to markdown templates (runatlantis#3478)
Browse files Browse the repository at this point in the history
* Capture stats in plan changes regexp

Signed-off-by: Leandro López (inkel) <[email protected]>

* Add PlanSuccessStats model to hold plan stats

This will be useful to pass down to the templates to include
additional information when formatting the messages emitted by Atlantis.

Signed-off-by: Leandro López (inkel) <[email protected]>

* Return stats for a PlanSuccess

Signed-off-by: Leandro López (inkel) <[email protected]>

* Add plan success stats to template data

Signed-off-by: Leandro López (inkel) <[email protected]>

* Refactor plan success template data creation

DRY. Also highlights a bug where the stats should be a field in
planSuccessData in order to be accessible from the templates.

Signed-off-by: Leandro López (inkel) <[email protected]>

* Move plan stats to planSuccessData struct

Signed-off-by: Leandro López (inkel) <[email protected]>

---------

Signed-off-by: Leandro López (inkel) <[email protected]>
Co-authored-by: PePe Amengual <[email protected]>
Co-authored-by: nitrocode <[email protected]>
  • Loading branch information
3 people authored Jun 3, 2023
1 parent 3468f58 commit 339c0fe
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 3 deletions.
14 changes: 12 additions & 2 deletions server/events/markdown_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type planSuccessData struct {
DisableApply bool
DisableRepoLocking bool
EnableDiffMarkdownFormat bool
PlanStats models.PlanSuccessStats
}

type policyCheckResultsData struct {
Expand Down Expand Up @@ -195,10 +196,19 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
}
if result.PlanSuccess != nil {
result.PlanSuccess.TerraformOutput = strings.TrimSpace(result.PlanSuccess.TerraformOutput)
data := planSuccessData{
PlanSuccess: *result.PlanSuccess,
PlanWasDeleted: common.PlansDeleted,
DisableApply: common.DisableApply,
DisableRepoLocking: common.DisableRepoLocking,
EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat,
PlanStats: result.PlanSuccess.Stats(),
}
if m.shouldUseWrappedTmpl(vcsHost, result.PlanSuccess.TerraformOutput) {
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("planSuccessWrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanSummary: result.PlanSuccess.Summary(), PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
data.PlanSummary = result.PlanSuccess.Summary()
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("planSuccessWrapped"), data)
} else {
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("planSuccessUnwrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("planSuccessUnwrapped"), data)
}
resultData.NoChanges = result.PlanSuccess.NoChanges()
numPlanSuccesses++
Expand Down
34 changes: 33 additions & 1 deletion server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/url"
paths "path"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -384,7 +385,7 @@ type PolicySetStatus struct {
// Summary regexes
var (
reChangesOutside = regexp.MustCompile(`Note: Objects have changed outside of Terraform`)
rePlanChanges = regexp.MustCompile(`Plan: \d+ to add, \d+ to change, \d+ to destroy.`)
rePlanChanges = regexp.MustCompile(`Plan: (\d+) to add, (\d+) to change, (\d+) to destroy.`)
reNoChanges = regexp.MustCompile(`No changes. (Infrastructure is up-to-date|Your infrastructure matches the configuration).`)
)

Expand Down Expand Up @@ -426,6 +427,11 @@ func (p PlanSuccess) DiffMarkdownFormattedTerraformOutput() string {
return strings.TrimSpace(formattedTerraformOutput)
}

// Stats returns plan change stats and contextual information.
func (p PlanSuccess) Stats() PlanSuccessStats {
return NewPlanSuccessStats(p.TerraformOutput)
}

// PolicyCheckResults is the result of a successful policy check run.
type PolicyCheckResults struct {
// PolicySetResults is the output from policy check binary(conftest|opa)
Expand Down Expand Up @@ -618,3 +624,29 @@ type WorkflowHookCommandContext struct {
// UUID for reference
HookID string
}

// PlanSuccessStats holds stats for a plan.
type PlanSuccessStats struct {
Add, Change, Destroy int
Changes, ChangesOutside bool
}

func NewPlanSuccessStats(output string) PlanSuccessStats {
m := rePlanChanges.FindStringSubmatch(output)

s := PlanSuccessStats{
ChangesOutside: reChangesOutside.MatchString(output),
Changes: len(m) > 0,
}

if s.Changes {
// We can skip checking the error here as we can assume
// Terraform output will always render an integer on these
// blocks.
s.Add, _ = strconv.Atoi(m[1])
s.Change, _ = strconv.Atoi(m[2])
s.Destroy, _ = strconv.Atoi(m[3])
}

return s
}
70 changes: 70 additions & 0 deletions server/events/models/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,73 @@ func TestPullStatus_StatusCount(t *testing.T) {
Equals(t, 1, ps.StatusCount(models.ErroredPolicyCheckStatus))
Equals(t, 1, ps.StatusCount(models.PassedPolicyCheckStatus))
}

func TestPlanSuccessStats(t *testing.T) {
tests := []struct {
name string
output string
exp models.PlanSuccessStats
}{
{
"has changes",
`An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- null_resource.hi[1]
Plan: 1 to add, 3 to change, 2 to destroy.`,
models.PlanSuccessStats{
Changes: true,
Add: 1,
Change: 3,
Destroy: 2,
},
},
{
"no changes",
`An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
No changes. Infrastructure is up-to-date.`,
models.PlanSuccessStats{},
},
{
"changes outside",
`Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the
last "terraform apply":
No changes. Your infrastructure matches the configuration.`,
models.PlanSuccessStats{
ChangesOutside: true,
},
},
{
"changes and changes outside",
`Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the
last "terraform apply":
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- null_resource.hi[1]
Plan: 3 to add, 0 to change, 1 to destroy.`,
models.PlanSuccessStats{
Changes: true,
ChangesOutside: true,

Add: 3,
Change: 0,
Destroy: 1,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := models.NewPlanSuccessStats(tt.output)
if s != tt.exp {
t.Errorf("\nexp: %#v\ngot: %#v", tt.exp, s)
}
})
}
}

0 comments on commit 339c0fe

Please sign in to comment.