Files
atlantis/server/events/markdown_renderer_test.go

4278 lines
107 KiB
Go

// Copyright 2017 HootSuite Media Inc.
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified hereafter by contributors to runatlantis/atlantis.
package events_test
import (
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
// Strip Carriage Returns, leading and trailing spaces and replace 'dollar' with 'backtick' in the string
func normalize(s string) string {
return strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(s, "$", "`"), "\r", ""))
}
func TestRenderErr(t *testing.T) {
err := errors.New("err")
cases := []struct {
Description string
Command command.Name
Error error
Expected string
}{
{
"apply error",
command.Apply,
err,
"**Apply Error**\n```\nerr\n```",
},
{
"plan error",
command.Plan,
err,
"**Plan Error**\n```\nerr\n```",
},
{
"policy check error",
command.PolicyCheck,
fmt.Errorf("some conftest error"),
"**Policy Check Error**\n```\nsome conftest error\n```",
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
res := command.Result{
Error: c.Error,
}
for _, verbose := range []bool{true, false} {
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log)), normalize(s))
}
})
}
}
}
func TestRenderFailure(t *testing.T) {
cases := []struct {
Description string
Command command.Name
Failure string
Expected string
}{
{
"apply failure",
command.Apply,
"failure",
"**Apply Failed**: failure",
},
{
"plan failure",
command.Plan,
"failure",
"**Plan Failed**: failure",
},
{
"policy check failure",
command.PolicyCheck,
"failure",
"**Policy Check Failed**: failure",
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
res := command.Result{
Failure: c.Failure,
}
for _, verbose := range []bool{true, false} {
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log)), normalize(s))
}
})
}
}
}
func TestRenderErrAndFailure(t *testing.T) {
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
res := command.Result{
Error: errors.New("error"),
Failure: "failure",
}
cmd := &events.CommentCommand{
Name: command.Plan,
Verbose: false,
}
s := r.Render(ctx, res, cmd)
Equals(t, "**Plan Error**\n```\nerror\n```", normalize(s))
}
func TestRenderProjectResults(t *testing.T) {
cases := []struct {
Description string
Command command.Name
SubCommand string
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"no projects",
command.Plan,
"",
[]command.ProjectResult{},
models.Github,
"Ran Plan for 0 projects:\n\n",
},
{
"single successful plan",
command.Plan,
"",
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful plan with main ahead",
command.Plan,
"",
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
MergedAgain: true,
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
:twisted_rightwards_arrows: Upstream was modified, a new merge was performed.
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful plan with project name",
command.Plan,
"",
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Plan for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful policy check with multiple policy sets and project name",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
// strings.Repeat require to get wrapped result
PolicyOutput: `FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions`,
Passed: false,
ReqApprovals: 1,
},
{
PolicySetName: "policy2",
// strings.Repeat require to get wrapped result
PolicyOutput: "2 tests, 2 passed, 0 warnings, 0 failure, 0 exceptions",
Passed: true,
ReqApprovals: 1,
},
},
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Policy Check for project: $projectname$ dir: $path$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
#### Policy Set: $policy2$
$$$diff
2 tests, 2 passed, 0 warnings, 0 failure, 0 exceptions
$$$
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
policy set: policy2: passed.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful policy check with project name",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
// strings.Repeat require to get wrapped result
PolicyOutput: strings.Repeat("line\n", 13) + `FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions`,
Passed: false,
ReqApprovals: 1,
},
},
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Policy Check for project: $projectname$ dir: $path$ workspace: $workspace$
<details><summary>Show Output</summary>
#### Policy Set: $policy1$
$$$diff
line
line
line
line
line
line
line
line
line
line
line
line
line
FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
</details>
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
$$$
policy set: policy1: 2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful import",
command.Import,
"",
[]command.ProjectResult{
{
ImportSuccess: &models.ImportSuccess{
Output: "import-output",
RePlanCmd: "atlantis plan -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Import for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
import-output
$$$
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"single successful state rm",
command.State,
"rm",
[]command.ProjectResult{
{
StateRmSuccess: &models.StateRmSuccess{
Output: "state-rm-output",
RePlanCmd: "atlantis plan -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran State $rm$ for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
state-rm-output
$$$
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"single successful apply",
command.Apply,
"",
[]command.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Apply for dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"single successful apply with project name",
command.Apply,
"",
[]command.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Apply for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"multiple successful plans",
command.Plan,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path2 -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url2)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"multiple successful policy checks",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
},
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
}, LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Policy Check for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path2 -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url2)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"multiple successful applies",
command.Apply,
"",
[]command.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
ProjectName: "projectname",
ApplySuccess: "success",
},
{
RepoRelDir: "path2",
Workspace: "workspace",
ApplySuccess: "success2",
},
},
models.Github,
`
Ran Apply for 2 projects:
1. project: $projectname$ dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
---
### 1. project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
$$$diff
success2
$$$
---
### Apply Summary
2 projects, 2 successful, 0 failed, 0 errored
`,
},
{
"single errored plan",
command.Plan,
"",
[]command.ProjectResult{
{
Error: errors.New("error"),
RepoRelDir: "path",
Workspace: "workspace",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
`,
},
{
"single failed plan",
command.Plan,
"",
[]command.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
Failure: "failure",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
**Plan Failed**: failure
`,
},
{
"successful, failed, and errored plan",
command.Plan,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Plan for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. project: $projectname$ dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Plan Failed**: failure
---
### 3. project: $projectname$ dir: $path3$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
---
### Plan Summary
3 projects, 1 with changes, 0 with no changes, 2 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"successful, failed, and errored policy check",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
}, LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions",
Passed: false,
ReqApprovals: 1,
},
}, LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Policy Check for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. project: $projectname$ dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Policy Check Failed**: failure
#### Policy Set: $policy1$
$$$diff
4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions
$$$
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 3. project: $projectname$ dir: $path3$ workspace: $workspace$
**Policy Check Error**
$$$
error
$$$
---
* :heavy_check_mark: To **approve** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis approve_policies
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan
$$$
`,
},
{
"successful, failed, and errored apply",
command.Apply,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
### Apply Summary
3 projects, 1 successful, 1 failed, 1 errored
`,
},
{
"successful, failed, and errored apply",
command.Apply,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
### Apply Summary
3 projects, 1 successful, 1 failed, 1 errored
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
SubName: c.SubCommand,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected+
fmt.Sprintf("<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log)), normalize(s))
}
})
}
})
}
}
func TestRenderProjectResultsWithQuietPolicyChecks(t *testing.T) {
cases := []struct {
Description string
Command command.Name
SubCommand string
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"single successful policy check with multiple policy sets and project name",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: `FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions`,
Passed: false,
ReqApprovals: 1,
},
{
PolicySetName: "policy2",
PolicyOutput: "2 tests, 2 passed, 0 warnings, 0 failure, 0 exceptions",
Passed: true,
ReqApprovals: 1,
},
},
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Policy Check for project: $projectname$ dir: $path$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
#### Policy Set: $policy2$
$$$diff
2 tests, 2 passed, 0 warnings, 0 failure, 0 exceptions
$$$
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
policy set: policy2: passed.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful policy check with project name",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
// strings.Repeat require to get wrapped result
PolicyOutput: strings.Repeat("line\n", 13) + `FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions`,
Passed: false,
ReqApprovals: 1,
},
},
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Policy Check for project: $projectname$ dir: $path$ workspace: $workspace$
<details><summary>Show Output</summary>
#### Policy Set: $policy1$
$$$diff
line
line
line
line
line
line
line
line
line
line
line
line
line
FAIL - <redacted plan file> - main - WARNING: Null Resource creation is prohibited.
2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
</details>
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
$$$
policy set: policy1: 2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"multiple successful policy checks",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
},
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
}, LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Policy Check for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"successful, failed, and errored policy check",
command.PolicyCheck,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
}, LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions",
Passed: false,
ReqApprovals: 1,
},
}, LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Policy Check for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. project: $projectname$ dir: $path3$ workspace: $workspace$
---
### 2. dir: $path2$ workspace: $workspace$
**Policy Check Failed**: failure
#### Policy Set: $policy1$
$$$diff
4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions
$$$
#### Policy Approval Status:
$$$
policy set: policy1: requires: 1 approval(s), have: 0.
$$$
* :heavy_check_mark: To **approve** this project, comment:
$$$shell
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 3. project: $projectname$ dir: $path3$ workspace: $workspace$
**Policy Check Error**
$$$
error
$$$
---
* :heavy_check_mark: To **approve** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis approve_policies
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan
$$$
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
true, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
SubName: c.SubCommand,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected+
fmt.Sprintf("<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log)), normalize(s))
}
})
}
})
}
}
// Test that if disable apply all is set then the apply all footer is not added
func TestRenderProjectResultsDisableApplyAll(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"single successful plan with disable apply all set",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"single successful plan with project name with disable apply all set",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Plan for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"multiple successful plans, disable apply all set",
command.Plan,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path2 -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url2)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
true, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected)+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log), normalize(s))
}
})
}
})
}
}
// Test that if disable apply is set then the apply footer is not added
func TestRenderProjectResultsDisableApply(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"single successful plan with disable apply set",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"single successful plan with project name with disable apply set",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Plan for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
`,
},
{
"multiple successful plans, disable apply set",
command.Plan,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url2)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
true, // disableApplyAll
true, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected)+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log), normalize(s))
}
})
}
})
}
}
// Run policy check with a custom template to validate custom template rendering.
func TestRenderCustomPolicyCheckTemplate_DisableApplyAll(t *testing.T) {
var exp string
tmpDir := t.TempDir()
filePath := fmt.Sprintf("%s/templates.tmpl", tmpDir)
_, err := os.Create(filePath)
Ok(t, err)
err = os.WriteFile(filePath, []byte("{{ define \"PolicyCheckResultsUnwrapped\" -}}somecustometext{{- end}}\n"), 0600)
Ok(t, err)
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
true, // disableApplyAll
true, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
tmpDir, // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
res := command.Result{
ProjectResults: []command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckResults: &models.PolicyCheckResults{
PolicySetResults: []models.PolicySetResult{
{
PolicySetName: "policy1",
PolicyOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
Passed: true,
},
}, LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
},
}
cmd := &events.CommentCommand{
Name: command.PolicyCheck,
Verbose: false,
}
rendered := r.Render(ctx, res, cmd)
exp = `
Ran Policy Check for dir: $path$ workspace: $workspace$
#### Policy Set: $policy1$
$$$diff
4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To re-run policies **plan** this project again by commenting:
$$$shell
atlantis plan -d path -w workspace
$$$
`
Equals(t, normalize(exp), normalize(rendered))
}
// Test that if folding is disabled that it's not used.
func TestRenderProjectResults_DisableFolding(t *testing.T) {
mr := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
true, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
res := command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "default",
Error: errors.New(strings.Repeat("line\n", 13)),
},
},
}
cmd := &events.CommentCommand{
Name: command.Plan,
Verbose: false,
}
rendered := mr.Render(ctx, res, cmd)
Equals(t, false, strings.Contains(rendered, "\n<details>"))
}
// Test that if the output is longer than 12 lines, it gets wrapped on the right
// VCS hosts during an error.
func TestRenderProjectResults_WrappedErr(t *testing.T) {
cases := []struct {
VCSHost models.VCSHostType
GitlabCommonMarkSupport bool
Output string
ShouldWrap bool
}{
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%s_%v", c.VCSHost.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.NewMarkdownRenderer(
c.GitlabCommonMarkSupport, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: c.VCSHost,
},
},
},
}
res := command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "default",
Error: errors.New(c.Output),
},
},
}
cmd := &events.CommentCommand{
Name: command.Plan,
Verbose: false,
}
rendered := mr.Render(ctx, res, cmd)
var exp string
if c.ShouldWrap {
exp = `
Ran Plan for dir: $.$ workspace: $default$
**Plan Error**
<details><summary>Show Output</summary>
$$$
` + c.Output + `
$$$
</details>
`
} else {
exp = `Ran Plan for dir: $.$ workspace: $default$
**Plan Error**
$$$
` + c.Output + `
$$$
`
}
Equals(t, normalize(exp), normalize(rendered))
})
}
}
// Test that if the output is longer than 12 lines, it gets wrapped on the right
// VCS hosts for a single project.
func TestRenderProjectResults_WrapSingleProject(t *testing.T) {
cases := []struct {
VCSHost models.VCSHostType
VcsRequestType string
GitlabCommonMarkSupport bool
Output string
ShouldWrap bool
}{
{
VCSHost: models.Github,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Github,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 13) + "No changes. Infrastructure is up-to-date.",
ShouldWrap: true,
},
{
VCSHost: models.Gitlab,
VcsRequestType: "Merge Request",
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
VcsRequestType: "Merge Request",
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
VcsRequestType: "Merge Request",
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
VcsRequestType: "Merge Request",
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 13) + "No changes. Infrastructure is up-to-date.",
ShouldWrap: true,
},
{
VCSHost: models.BitbucketCloud,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketCloud,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
VcsRequestType: "Pull Request",
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
}
for _, c := range cases {
for _, cmdName := range []command.Name{command.Plan, command.Apply} {
t.Run(fmt.Sprintf("%s_%s_%v", c.VCSHost.String(), cmdName.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.NewMarkdownRenderer(
c.GitlabCommonMarkSupport, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: c.VCSHost,
},
},
},
}
var pr command.ProjectResult
switch cmdName {
case command.Plan:
pr = command.ProjectResult{
RepoRelDir: ".",
Workspace: "default",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: c.Output,
LockURL: "lock-url",
RePlanCmd: "replancmd",
ApplyCmd: "applycmd",
},
}
case command.Apply:
pr = command.ProjectResult{
RepoRelDir: ".",
Workspace: "default",
ApplySuccess: c.Output,
}
}
res := command.Result{
ProjectResults: []command.ProjectResult{pr},
}
cmd := &events.CommentCommand{
Name: cmdName,
Verbose: false,
}
rendered := mr.Render(ctx, res, cmd)
// Check result.
var exp string
switch cmdName {
case command.Plan:
if c.ShouldWrap {
exp = `
Ran Plan for dir: $.$ workspace: $default$
<details><summary>Show Output</summary>
$$$diff
` + strings.TrimSpace(c.Output) + `
$$$
</details>
* :arrow_forward: To **apply** this plan, comment:
$$$shell
applycmd
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
replancmd
$$$
No changes. Infrastructure is up-to-date.
---
* :fast_forward: To **apply** all unapplied plans from this ` + c.VcsRequestType + `, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this ` + c.VcsRequestType + `, comment:
$$$shell
atlantis unlock
$$$
`
} else {
exp = `
Ran Plan for dir: $.$ workspace: $default$
$$$diff
` + strings.TrimSpace(c.Output) + `
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
applycmd
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
replancmd
$$$
---
* :fast_forward: To **apply** all unapplied plans from this ` + c.VcsRequestType + `, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this ` + c.VcsRequestType + `, comment:
$$$shell
atlantis unlock
$$$
`
}
case command.Apply:
if c.ShouldWrap {
exp = `
Ran Apply for dir: $.$ workspace: $default$
<details><summary>Show Output</summary>
$$$diff
` + strings.TrimSpace(c.Output) + `
$$$
</details>
`
} else {
exp = `
Ran Apply for dir: $.$ workspace: $default$
$$$diff
` + strings.TrimSpace(c.Output) + `
$$$
`
}
}
Equals(t, normalize(exp), normalize(rendered))
})
}
}
}
func TestRenderProjectResults_MultiProjectApplyWrapped(t *testing.T) {
mr := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
tfOut := strings.Repeat("line\n", 13)
res := command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
ApplySuccess: tfOut,
},
{
RepoRelDir: ".",
Workspace: "production",
ApplySuccess: tfOut,
},
},
}
cmd := &events.CommentCommand{
Name: command.Apply,
Verbose: false,
}
rendered := mr.Render(ctx, res, cmd)
exp := `
Ran Apply for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
---
### 1. dir: $.$ workspace: $staging$
<details><summary>Show Output</summary>
$$$diff
` + strings.TrimSpace(tfOut) + `
$$$
</details>
---
### 2. dir: $.$ workspace: $production$
<details><summary>Show Output</summary>
$$$diff
` + strings.TrimSpace(tfOut) + `
$$$
</details>
---
### Apply Summary
2 projects, 2 successful, 0 failed, 0 errored
`
Equals(t, normalize(exp), normalize(rendered))
}
func TestRenderProjectResults_MultiProjectPlanWrapped(t *testing.T) {
mr := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
tfOut := strings.Repeat("line\n", 13) + "Plan: 1 to add, 0 to change, 0 to destroy."
res := command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: tfOut,
LockURL: "staging-lock-url",
ApplyCmd: "staging-apply-cmd",
RePlanCmd: "staging-replan-cmd",
},
},
{
RepoRelDir: ".",
Workspace: "production",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: tfOut,
LockURL: "production-lock-url",
ApplyCmd: "production-apply-cmd",
RePlanCmd: "production-replan-cmd",
},
},
},
}
cmd := &events.CommentCommand{
Name: command.Plan,
Verbose: false,
}
rendered := mr.Render(ctx, res, cmd)
exp := `
Ran Plan for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
---
### 1. dir: $.$ workspace: $staging$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
</details>
* :arrow_forward: To **apply** this plan, comment:
$$$shell
staging-apply-cmd
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](staging-lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
staging-replan-cmd
$$$
Plan: 1 to add, 0 to change, 0 to destroy.
---
### 2. dir: $.$ workspace: $production$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
</details>
* :arrow_forward: To **apply** this plan, comment:
$$$shell
production-apply-cmd
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](production-lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
production-replan-cmd
$$$
Plan: 1 to add, 0 to change, 0 to destroy.
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`
Equals(t, normalize(exp), normalize(rendered))
}
// Test rendering when there was an error in one of the plans and we deleted
// all the plans as a result.
func TestRenderProjectResults_PlansDeleted(t *testing.T) {
cases := map[string]struct {
res command.Result
exp string
}{
"one failure": {
res: command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
Failure: "failure",
},
},
PlansDeleted: true,
},
exp: `
Ran Plan for dir: $.$ workspace: $staging$
**Plan Failed**: failure
`,
},
"two failures": {
res: command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
Failure: "failure",
},
{
RepoRelDir: ".",
Workspace: "production",
Failure: "failure",
},
},
PlansDeleted: true,
},
exp: `
Ran Plan for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
---
### 1. dir: $.$ workspace: $staging$
**Plan Failed**: failure
---
### 2. dir: $.$ workspace: $production$
**Plan Failed**: failure
---
### Plan Summary
2 projects, 0 with changes, 0 with no changes, 2 failed
`,
},
"one failure, one success": {
res: command.Result{
ProjectResults: []command.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
Failure: "failure",
},
{
RepoRelDir: ".",
Workspace: "production",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "tf out",
LockURL: "lock-url",
RePlanCmd: "re-plan cmd",
ApplyCmd: "apply cmd",
},
},
},
PlansDeleted: true,
},
exp: `
Ran Plan for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
---
### 1. dir: $.$ workspace: $staging$
**Plan Failed**: failure
---
### 2. dir: $.$ workspace: $production$
$$$diff
tf out
$$$
This plan was not saved because one or more projects failed and automerge requires all plans pass.
---
### Plan Summary
2 projects, 1 with changes, 0 with no changes, 1 failed
`,
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
mr := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
cmd := &events.CommentCommand{
Name: command.Plan,
Verbose: false,
}
rendered := mr.Render(ctx, c.res, cmd)
Equals(t, normalize(c.exp), normalize(rendered))
})
}
}
// test that id repo locking is disabled the link to unlock the project is not rendered
func TestRenderProjectResultsWithRepoLockingDisabled(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"no projects",
command.Plan,
[]command.ProjectResult{},
models.Github,
"Ran Plan for 0 projects:\n\n",
},
{
"single successful plan",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful plan with main ahead",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
MergedAgain: true,
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
:twisted_rightwards_arrows: Upstream was modified, a new merge was performed.
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful plan with project name",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Plan for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"single successful apply",
command.Apply,
[]command.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Apply for dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"single successful apply with project name",
command.Apply,
[]command.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`
Ran Apply for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"multiple successful plans",
command.Plan,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path2 -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"multiple successful applies",
command.Apply,
[]command.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
ProjectName: "projectname",
ApplySuccess: "success",
},
{
RepoRelDir: "path2",
Workspace: "workspace",
ApplySuccess: "success2",
},
},
models.Github,
`
Ran Apply for 2 projects:
1. project: $projectname$ dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
---
### 1. project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
$$$diff
success2
$$$
---
### Apply Summary
2 projects, 2 successful, 0 failed, 0 errored
`,
},
{
"single errored plan",
command.Plan,
[]command.ProjectResult{
{
Error: errors.New("error"),
RepoRelDir: "path",
Workspace: "workspace",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
`,
},
{
"single failed plan",
command.Plan,
[]command.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
Failure: "failure",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
**Plan Failed**: failure
`,
},
{
"successful, failed, and errored plan",
command.Plan,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Plan for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. project: $projectname$ dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Plan Failed**: failure
---
### 3. project: $projectname$ dir: $path3$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
---
### Plan Summary
3 projects, 1 with changes, 0 with no changes, 2 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"successful, failed, and errored apply",
command.Apply,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
### Apply Summary
3 projects, 1 successful, 1 failed, 1 errored
`,
},
{
"successful, failed, and errored apply",
command.Apply,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`
Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
### Apply Summary
3 projects, 1 successful, 1 failed, 1 errored
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
true, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected+
fmt.Sprintf("<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log)), normalize(s))
}
})
}
})
}
}
func TestRenderProjectResultsWithGitLab(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"multiple successful plans",
command.Plan,
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Gitlab,
`
Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path2 -w workspace
$$$
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path2 -w workspace
$$$
---
### Plan Summary
2 projects, 2 with changes, 0 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Merge Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Merge Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
true, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: c.VCSHost,
},
},
},
}
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected)+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log), normalize(s))
}
})
}
})
}
}
const tfOutput = `
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# module.redacted.aws_instance.redacted must be replaced
-/+ resource "aws_instance" "redacted" {
~ ami = "ami-redacted" -> "ami-redacted" # forces replacement
~ arn = "arn:aws:ec2:us-east-1:redacted:instance/i-redacted" -> (known after apply)
~ associate_public_ip_address = false -> (known after apply)
availability_zone = "us-east-1b"
~ cpu_core_count = 4 -> (known after apply)
~ cpu_threads_per_core = 2 -> (known after apply)
- disable_api_termination = false -> null
- ebs_optimized = false -> null
get_password_data = false
- hibernation = false -> null
+ host_id = (known after apply)
iam_instance_profile = "remote_redacted_profile"
~ id = "i-redacted" -> (known after apply)
~ instance_state = "running" -> (known after apply)
instance_type = "c5.2xlarge"
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
key_name = "RedactedRedactedRedacted"
- monitoring = false -> null
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
~ primary_network_interface_id = "eni-redacted" -> (known after apply)
~ private_dns = "ip-redacted.ec2.internal" -> (known after apply)
~ private_ip = "redacted" -> (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
~ secondary_private_ips = [] -> (known after apply)
~ security_groups = [] -> (known after apply)
source_dest_check = true
subnet_id = "subnet-redacted"
tags = {
"Name" = "redacted-redacted"
}
~ tenancy = "default" -> (known after apply)
user_data = "redacted"
~ volume_tags = {} -> (known after apply)
vpc_security_group_ids = [
"sg-redactedsecuritygroup",
]
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
~ metadata_options {
~ http_endpoint = "enabled" -> (known after apply)
~ http_put_response_hop_limit = 1 -> (known after apply)
~ http_tokens = "optional" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
~ root_block_device {
~ delete_on_termination = true -> (known after apply)
~ device_name = "/dev/sda1" -> (known after apply)
~ encrypted = false -> (known after apply)
~ iops = 600 -> (known after apply)
+ kms_key_id = (known after apply)
~ volume_id = "vol-redacted" -> (known after apply)
~ volume_size = 200 -> (known after apply)
~ volume_type = "gp2" -> (known after apply)
}
}
# module.redacted.aws_route53_record.redacted_record will be updated in-place
~ resource "aws_route53_record" "redacted_record" {
fqdn = "redacted.redacted.redacted.io"
id = "redacted_redacted.redacted.redacted.io_A"
name = "redacted.redacted.redacted.io"
~ records = [
"foo",
- "redacted",
] -> (known after apply)
ttl = 300
type = "A"
zone_id = "redacted"
}
# module.redacted.aws_route53_record.redacted_record_2 will be created
+ resource "aws_route53_record" "redacted_record" {
+ fqdn = "redacted.redacted.redacted.io"
+ id = "redacted_redacted.redacted.redacted.io_A"
+ name = "redacted.redacted.redacted.io"
+ records = [
"foo",
]
+ ttl = 300
+ type = "A"
+ zone_id = "redacted"
}
# helm_release.external_dns[0] will be updated in-place
~ resource "helm_release" "external_dns" {
id = "external-dns"
name = "external-dns"
~ values = [
- <<-EOT
image:
tag: "0.12.0"
pullSecrets:
- XXXXX
domainFilters: ["xxxxx","xxxxx"]
base64:
+dGhpcyBpcyBzb21lIHN0cmluZyBvciBzb21ldGhpbmcKCg==
EOT,
+ <<-EOT
image:
tag: "0.12.0"
pullSecrets:
- XXXXX
domainFilters: ["xxxxx","xxxxx"]
base64:
+dGhpcyBpcyBzb21lIHN0cmluZyBvciBzb21ldGhpbmcKCg==
EOT,
]
}
# aws_api_gateway_rest_api.rest_api will be updated in-place
~ resource "aws_api_gateway_rest_api" "rest_api" {
~ body = <<-EOT
openapi: 3.0.0
security:
- SomeAuth: []
paths:
/someEndpoint:
get:
- operationId: someOperation
+ operationId: someOperation2
responses:
204:
description: Empty response.
components:
schemas:
SomeEnum:
type: string
enum:
- value1
- value2
securitySchemes:
SomeAuth:
type: apiKey
in: header
name: Authorization
EOT
id = "4i5suz5c4l"
name = "test"
tags = {}
# (9 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Plan: 1 to add, 2 to change, 1 to destroy.
`
var cases = []struct {
Description string
Command command.Name
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"single successful plan with diff markdown formatted",
command.Plan,
[]command.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: tfOutput,
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`
Ran Plan for dir: $path$ workspace: $workspace$
<details><summary>Show Output</summary>
$$$diff
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
! update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# module.redacted.aws_instance.redacted must be replaced
-/+ resource "aws_instance" "redacted" {
! ami = "ami-redacted" -> "ami-redacted" # forces replacement
! arn = "arn:aws:ec2:us-east-1:redacted:instance/i-redacted" -> (known after apply)
! associate_public_ip_address = false -> (known after apply)
availability_zone = "us-east-1b"
! cpu_core_count = 4 -> (known after apply)
! cpu_threads_per_core = 2 -> (known after apply)
- disable_api_termination = false -> null
- ebs_optimized = false -> null
get_password_data = false
- hibernation = false -> null
+ host_id = (known after apply)
iam_instance_profile = "remote_redacted_profile"
! id = "i-redacted" -> (known after apply)
! instance_state = "running" -> (known after apply)
instance_type = "c5.2xlarge"
! ipv6_address_count = 0 -> (known after apply)
! ipv6_addresses = [] -> (known after apply)
key_name = "RedactedRedactedRedacted"
- monitoring = false -> null
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
! primary_network_interface_id = "eni-redacted" -> (known after apply)
! private_dns = "ip-redacted.ec2.internal" -> (known after apply)
! private_ip = "redacted" -> (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
! secondary_private_ips = [] -> (known after apply)
! security_groups = [] -> (known after apply)
source_dest_check = true
subnet_id = "subnet-redacted"
tags = {
"Name" = "redacted-redacted"
}
! tenancy = "default" -> (known after apply)
user_data = "redacted"
! volume_tags = {} -> (known after apply)
vpc_security_group_ids = [
"sg-redactedsecuritygroup",
]
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
! metadata_options {
! http_endpoint = "enabled" -> (known after apply)
! http_put_response_hop_limit = 1 -> (known after apply)
! http_tokens = "optional" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
! root_block_device {
! delete_on_termination = true -> (known after apply)
! device_name = "/dev/sda1" -> (known after apply)
! encrypted = false -> (known after apply)
! iops = 600 -> (known after apply)
+ kms_key_id = (known after apply)
! volume_id = "vol-redacted" -> (known after apply)
! volume_size = 200 -> (known after apply)
! volume_type = "gp2" -> (known after apply)
}
}
# module.redacted.aws_route53_record.redacted_record will be updated in-place
! resource "aws_route53_record" "redacted_record" {
fqdn = "redacted.redacted.redacted.io"
id = "redacted_redacted.redacted.redacted.io_A"
name = "redacted.redacted.redacted.io"
! records = [
"foo",
- "redacted",
] -> (known after apply)
ttl = 300
type = "A"
zone_id = "redacted"
}
# module.redacted.aws_route53_record.redacted_record_2 will be created
+ resource "aws_route53_record" "redacted_record" {
+ fqdn = "redacted.redacted.redacted.io"
+ id = "redacted_redacted.redacted.redacted.io_A"
+ name = "redacted.redacted.redacted.io"
+ records = [
"foo",
]
+ ttl = 300
+ type = "A"
+ zone_id = "redacted"
}
# helm_release.external_dns[0] will be updated in-place
! resource "helm_release" "external_dns" {
id = "external-dns"
name = "external-dns"
! values = [
- <<-EOT
image:
tag: "0.12.0"
pullSecrets:
- XXXXX
domainFilters: ["xxxxx","xxxxx"]
base64:
+dGhpcyBpcyBzb21lIHN0cmluZyBvciBzb21ldGhpbmcKCg==
EOT,
+ <<-EOT
image:
tag: "0.12.0"
pullSecrets:
- XXXXX
domainFilters: ["xxxxx","xxxxx"]
base64:
+dGhpcyBpcyBzb21lIHN0cmluZyBvciBzb21ldGhpbmcKCg==
EOT,
]
}
# aws_api_gateway_rest_api.rest_api will be updated in-place
! resource "aws_api_gateway_rest_api" "rest_api" {
! body = <<-EOT
openapi: 3.0.0
security:
- SomeAuth: []
paths:
/someEndpoint:
get:
- operationId: someOperation
+ operationId: someOperation2
responses:
204:
description: Empty response.
components:
schemas:
SomeEnum:
type: string
enum:
- value1
- value2
securitySchemes:
SomeAuth:
type: apiKey
in: header
name: Authorization
EOT
id = "4i5suz5c4l"
name = "test"
tags = {}
# (9 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Plan: 1 to add, 2 to change, 1 to destroy.
$$$
</details>
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
Plan: 1 to add, 2 to change, 1 to destroy.
`,
},
}
func TestRenderProjectResultsWithEnableDiffMarkdownFormat(t *testing.T) {
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
true, // disableApplyAll
true, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
true, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: models.Github,
},
},
},
}
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected)+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log), normalize(s))
}
})
}
})
}
}
var Render string
func BenchmarkRenderProjectResultsWithEnableDiffMarkdownFormat(b *testing.B) {
var render string
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
true, // disableApplyAll
true, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
true, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
false, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(b).WithHistory()
logText := "log"
logger.Info(logText)
for _, c := range cases {
b.Run(c.Description, func(b *testing.B) {
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: c.VCSHost,
},
},
},
}
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
b.Run(fmt.Sprintf("verbose %t", verbose), func(b *testing.B) {
cmd := &events.CommentCommand{
Name: c.Command,
Verbose: verbose,
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
render = r.Render(ctx, res, cmd)
}
Render = render
})
}
})
}
}
func TestRenderProjectResultsHideUnchangedPlans(t *testing.T) {
cases := []struct {
Description string
Command command.Name
SubCommand string
ProjectResults []command.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"multiple successful plans, hide unchanged plans",
command.Plan,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "No changes. Infrastructure is up-to-date.",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname2",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output3",
LockURL: "lock-url3",
ApplyCmd: "atlantis apply -d path3 -w workspace",
RePlanCmd: "atlantis plan -d path3 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 3 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
1. project: $projectname2$ dir: $path3$ workspace: $workspace$
---
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path -w workspace
$$$
---
### 3. project: $projectname2$ dir: $path3$ workspace: $workspace$
$$$diff
terraform-output3
$$$
* :arrow_forward: To **apply** this plan, comment:
$$$shell
atlantis apply -d path3 -w workspace
$$$
* :put_litter_in_its_place: To **delete** this plan and lock, click [here](lock-url3)
* :repeat: To **plan** this project again, comment:
$$$shell
atlantis plan -d path3 -w workspace
$$$
---
### Plan Summary
3 projects, 2 with changes, 1 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
{
"multiple successful plans, hide unchanged plans, all plans are unchanged",
command.Plan,
"",
[]command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "No changes. Infrastructure is up-to-date.",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "No changes. Infrastructure is up-to-date.",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname2",
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "No changes. Infrastructure is up-to-date.",
LockURL: "lock-url3",
ApplyCmd: "atlantis apply -d path3 -w workspace",
RePlanCmd: "atlantis plan -d path3 -w workspace",
},
},
},
models.Github,
`
Ran Plan for 3 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
1. project: $projectname2$ dir: $path3$ workspace: $workspace$
---
### Plan Summary
3 projects, 0 with changes, 3 with no changes, 0 failed
* :fast_forward: To **apply** all unapplied plans from this Pull Request, comment:
$$$shell
atlantis apply
$$$
* :put_litter_in_its_place: To **delete** all plans and locks from this Pull Request, comment:
$$$shell
atlantis unlock
$$$
`,
},
}
r := events.NewMarkdownRenderer(
false, // gitlabSupportsCommonMark
false, // disableApplyAll
false, // disableApply
false, // disableMarkdownFolding
false, // disableRepoLocking
false, // enableDiffMarkdownFormat
"", // markdownTemplateOverridesDir
"atlantis", // executableName
true, // hideUnchangedPlanComments
false, // quietPolicyChecks
)
logger := logging.NewNoopLogger(t).WithHistory()
logText := "log"
logger.Info(logText)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
ctx := &command.Context{
Log: logger,
Pull: models.PullRequest{
BaseRepo: models.Repo{
VCSHost: models.VCSHost{
Type: c.VCSHost,
},
},
},
}
res := command.Result{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
cmd := &events.CommentCommand{
Name: c.Command,
SubName: c.SubCommand,
Verbose: verbose,
}
s := r.Render(ctx, res, cmd)
if !verbose {
Equals(t, normalize(c.Expected), normalize(s))
} else {
log := fmt.Sprintf("[INFO] %s", logText)
Equals(t, normalize(c.Expected)+
fmt.Sprintf("\n<details><summary>Log</summary>\n<p>\n\n```\n%s\n```\n</p></details>", log), normalize(s))
}
})
}
})
}
}