Add custom templating support (#2647)

* added custom templates support

* made default templates DRY

* Change init to anonymous function.

* Initialize anonymous function inside var

* - Add flag/cfg param for custom template dir
- Add test for custom template

* Add documentation.

* Address PR comments.

Co-authored-by: Ricard Bejarano <ribejara@thousandeyes.com>
This commit is contained in:
Ross Strickland
2022-11-11 09:56:33 -06:00
committed by GitHub
parent 5e3f1a9f90
commit d620d5de98
34 changed files with 593 additions and 354 deletions

View File

@@ -37,63 +37,64 @@ import (
// 3. Add your flag's description etc. to the stringFlags, intFlags, or boolFlags slices.
const (
// Flag names.
ADWebhookPasswordFlag = "azuredevops-webhook-password" // nolint: gosec
ADWebhookUserFlag = "azuredevops-webhook-user"
ADTokenFlag = "azuredevops-token" // nolint: gosec
ADUserFlag = "azuredevops-user"
ADHostnameFlag = "azuredevops-hostname"
AllowForkPRsFlag = "allow-fork-prs"
AllowRepoConfigFlag = "allow-repo-config"
AtlantisURLFlag = "atlantis-url"
AutomergeFlag = "automerge"
AutoplanFileListFlag = "autoplan-file-list"
BitbucketBaseURLFlag = "bitbucket-base-url"
BitbucketTokenFlag = "bitbucket-token"
BitbucketUserFlag = "bitbucket-user"
BitbucketWebhookSecretFlag = "bitbucket-webhook-secret"
ConfigFlag = "config"
CheckoutStrategyFlag = "checkout-strategy"
DataDirFlag = "data-dir"
DefaultTFVersionFlag = "default-tf-version"
DisableApplyAllFlag = "disable-apply-all"
DisableApplyFlag = "disable-apply"
DisableAutoplanFlag = "disable-autoplan"
DisableMarkdownFoldingFlag = "disable-markdown-folding"
DisableRepoLockingFlag = "disable-repo-locking"
EnablePolicyChecksFlag = "enable-policy-checks"
EnableRegExpCmdFlag = "enable-regexp-cmd"
EnableDiffMarkdownFormat = "enable-diff-markdown-format"
GHHostnameFlag = "gh-hostname"
GHTeamAllowlistFlag = "gh-team-allowlist"
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
GHAppIDFlag = "gh-app-id"
GHAppKeyFlag = "gh-app-key"
GHAppKeyFileFlag = "gh-app-key-file"
GHAppSlugFlag = "gh-app-slug"
GHOrganizationFlag = "gh-org"
GHWebhookSecretFlag = "gh-webhook-secret" // nolint: gosec
GHAllowMergeableBypassApply = "gh-allow-mergeable-bypass-apply" // nolint: gosec
GitlabHostnameFlag = "gitlab-hostname"
GitlabTokenFlag = "gitlab-token"
GitlabUserFlag = "gitlab-user"
GitlabWebhookSecretFlag = "gitlab-webhook-secret" // nolint: gosec
APISecretFlag = "api-secret"
HidePrevPlanComments = "hide-prev-plan-comments"
LockingDBType = "locking-db-type"
LogLevelFlag = "log-level"
ParallelPoolSize = "parallel-pool-size"
StatsNamespace = "stats-namespace"
AllowDraftPRs = "allow-draft-prs"
PortFlag = "port"
RedisDB = "redis-db"
RedisHost = "redis-host"
RedisPassword = "redis-password"
RedisPort = "redis-port"
RedisTLSEnabled = "redis-tls-enabled"
RedisInsecureSkipVerify = "redis-insecure-skip-verify"
RepoConfigFlag = "repo-config"
RepoConfigJSONFlag = "repo-config-json"
ADWebhookPasswordFlag = "azuredevops-webhook-password" // nolint: gosec
ADWebhookUserFlag = "azuredevops-webhook-user"
ADTokenFlag = "azuredevops-token" // nolint: gosec
ADUserFlag = "azuredevops-user"
ADHostnameFlag = "azuredevops-hostname"
AllowForkPRsFlag = "allow-fork-prs"
AllowRepoConfigFlag = "allow-repo-config"
AtlantisURLFlag = "atlantis-url"
AutomergeFlag = "automerge"
AutoplanFileListFlag = "autoplan-file-list"
BitbucketBaseURLFlag = "bitbucket-base-url"
BitbucketTokenFlag = "bitbucket-token"
BitbucketUserFlag = "bitbucket-user"
BitbucketWebhookSecretFlag = "bitbucket-webhook-secret"
ConfigFlag = "config"
CheckoutStrategyFlag = "checkout-strategy"
DataDirFlag = "data-dir"
DefaultTFVersionFlag = "default-tf-version"
DisableApplyAllFlag = "disable-apply-all"
DisableApplyFlag = "disable-apply"
DisableAutoplanFlag = "disable-autoplan"
DisableMarkdownFoldingFlag = "disable-markdown-folding"
DisableRepoLockingFlag = "disable-repo-locking"
EnablePolicyChecksFlag = "enable-policy-checks"
EnableRegExpCmdFlag = "enable-regexp-cmd"
EnableDiffMarkdownFormat = "enable-diff-markdown-format"
GHHostnameFlag = "gh-hostname"
GHTeamAllowlistFlag = "gh-team-allowlist"
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
GHAppIDFlag = "gh-app-id"
GHAppKeyFlag = "gh-app-key"
GHAppKeyFileFlag = "gh-app-key-file"
GHAppSlugFlag = "gh-app-slug"
GHOrganizationFlag = "gh-org"
GHWebhookSecretFlag = "gh-webhook-secret" // nolint: gosec
GHAllowMergeableBypassApply = "gh-allow-mergeable-bypass-apply" // nolint: gosec
GitlabHostnameFlag = "gitlab-hostname"
GitlabTokenFlag = "gitlab-token"
GitlabUserFlag = "gitlab-user"
GitlabWebhookSecretFlag = "gitlab-webhook-secret" // nolint: gosec
APISecretFlag = "api-secret"
HidePrevPlanComments = "hide-prev-plan-comments"
LockingDBType = "locking-db-type"
LogLevelFlag = "log-level"
MarkdownTemplateOverridesDirFlag = "markdown-template-overrides-dir"
ParallelPoolSize = "parallel-pool-size"
StatsNamespace = "stats-namespace"
AllowDraftPRs = "allow-draft-prs"
PortFlag = "port"
RedisDB = "redis-db"
RedisHost = "redis-host"
RedisPassword = "redis-password"
RedisPort = "redis-port"
RedisTLSEnabled = "redis-tls-enabled"
RedisInsecureSkipVerify = "redis-insecure-skip-verify"
RepoConfigFlag = "repo-config"
RepoConfigJSONFlag = "repo-config-json"
// RepoWhitelistFlag is deprecated for RepoAllowlistFlag.
RepoWhitelistFlag = "repo-whitelist"
RepoAllowlistFlag = "repo-allowlist"
@@ -122,30 +123,31 @@ const (
WebsocketCheckOrigin = "websocket-check-origin"
// NOTE: Must manually set these as defaults in the setDefaults function.
DefaultADBasicUser = ""
DefaultADBasicPassword = ""
DefaultADHostname = "dev.azure.com"
DefaultAutoplanFileList = "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl"
DefaultCheckoutStrategy = "branch"
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
DefaultDataDir = "~/.atlantis"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLockingDBType = "boltdb"
DefaultLogLevel = "info"
DefaultParallelPoolSize = 15
DefaultStatsNamespace = "atlantis"
DefaultPort = 4141
DefaultRedisDB = 0
DefaultRedisPort = 6379
DefaultRedisTLSEnabled = false
DefaultRedisInsecureSkipVerify = false
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
DefaultVCSStatusName = "atlantis"
DefaultWebBasicAuth = false
DefaultWebUsername = "atlantis"
DefaultWebPassword = "atlantis"
DefaultADBasicUser = ""
DefaultADBasicPassword = ""
DefaultADHostname = "dev.azure.com"
DefaultAutoplanFileList = "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl"
DefaultCheckoutStrategy = "branch"
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
DefaultDataDir = "~/.atlantis"
DefaultMarkdownTemplateOverridesDir = "~/.markdown_templates"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLockingDBType = "boltdb"
DefaultLogLevel = "info"
DefaultParallelPoolSize = 15
DefaultStatsNamespace = "atlantis"
DefaultPort = 4141
DefaultRedisDB = 0
DefaultRedisPort = 6379
DefaultRedisTLSEnabled = false
DefaultRedisInsecureSkipVerify = false
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
DefaultVCSStatusName = "atlantis"
DefaultWebBasicAuth = false
DefaultWebUsername = "atlantis"
DefaultWebPassword = "atlantis"
)
var stringFlags = map[string]stringFlag{
@@ -285,6 +287,10 @@ var stringFlags = map[string]stringFlag{
description: "Log level. Either debug, info, warn, or error.",
defaultValue: DefaultLogLevel,
},
MarkdownTemplateOverridesDirFlag: {
description: "Directory for custom overrides to the markdown templates used for comments.",
defaultValue: DefaultMarkdownTemplateOverridesDir,
},
StatsNamespace: {
description: "Namespace for aggregating stats.",
defaultValue: DefaultStatsNamespace,
@@ -671,6 +677,9 @@ func (s *ServerCmd) run() error {
if err := s.setDataDir(&userConfig); err != nil {
return err
}
if err := s.setMarkdownTemplateOverridesDir(&userConfig); err != nil {
return err
}
s.setVarFileAllowlist(&userConfig)
if err := s.deprecationWarnings(&userConfig); err != nil {
return err
@@ -722,6 +731,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
if c.LogLevel == "" {
c.LogLevel = DefaultLogLevel
}
if c.MarkdownTemplateOverridesDir == "" {
c.MarkdownTemplateOverridesDir = DefaultMarkdownTemplateOverridesDir
}
if c.ParallelPoolSize == 0 {
c.ParallelPoolSize = DefaultParallelPoolSize
}
@@ -887,6 +899,30 @@ func (s *ServerCmd) setDataDir(userConfig *server.UserConfig) error {
return nil
}
// setMarkdownTemplateOverridesDir checks if ~ was used in markdown-template-overrides-dir and converts it to the actual
// home directory. If we don't do this, we'll create a directory called "~"
// instead of actually using home. It also converts relative paths to absolute.
func (s *ServerCmd) setMarkdownTemplateOverridesDir(userConfig *server.UserConfig) error {
finalPath := userConfig.MarkdownTemplateOverridesDir
// Convert ~ to the actual home dir.
if strings.HasPrefix(finalPath, "~/") {
var err error
finalPath, err = homedir.Expand(finalPath)
if err != nil {
return errors.Wrap(err, "determining home directory")
}
}
// Convert relative paths to absolute.
finalPath, err := filepath.Abs(finalPath)
if err != nil {
return errors.Wrap(err, "making markdown-template-overrides-dir absolute")
}
userConfig.MarkdownTemplateOverridesDir = finalPath
return nil
}
// setVarFileAllowlist checks if var-file-allowlist is unassigned and makes it default to data-dir for better backward
// compatibility.
func (s *ServerCmd) setVarFileAllowlist(userConfig *server.UserConfig) {

View File

@@ -51,66 +51,67 @@ func (s *ServerStarterMock) Start() error {
// Adding a new flag? Add it to this slice for testing in alphabetical
// order.
var testFlags = map[string]interface{}{
ADTokenFlag: "ad-token",
ADUserFlag: "ad-user",
ADWebhookPasswordFlag: "ad-wh-pass",
ADWebhookUserFlag: "ad-wh-user",
AtlantisURLFlag: "url",
AllowForkPRsFlag: true,
AllowRepoConfigFlag: true,
AutomergeFlag: true,
AutoplanFileListFlag: "**/*.tf,**/*.yml",
BitbucketBaseURLFlag: "https://bitbucket-base-url.com",
BitbucketTokenFlag: "bitbucket-token",
BitbucketUserFlag: "bitbucket-user",
BitbucketWebhookSecretFlag: "bitbucket-secret",
CheckoutStrategyFlag: "merge",
DataDirFlag: "/path",
DefaultTFVersionFlag: "v0.11.0",
DisableApplyAllFlag: true,
DisableApplyFlag: true,
DisableMarkdownFoldingFlag: true,
DisableRepoLockingFlag: true,
GHHostnameFlag: "ghhostname",
GHTokenFlag: "token",
GHUserFlag: "user",
GHAppIDFlag: int64(0),
GHAppKeyFlag: "",
GHAppKeyFileFlag: "",
GHAppSlugFlag: "atlantis",
GHOrganizationFlag: "",
GHWebhookSecretFlag: "secret",
GitlabHostnameFlag: "gitlab-hostname",
GitlabTokenFlag: "gitlab-token",
GitlabUserFlag: "gitlab-user",
GitlabWebhookSecretFlag: "gitlab-secret",
LockingDBType: "boltdb",
LogLevelFlag: "debug",
StatsNamespace: "atlantis",
AllowDraftPRs: true,
PortFlag: 8181,
ParallelPoolSize: 100,
RepoAllowlistFlag: "github.com/runatlantis/atlantis",
RequireApprovalFlag: true,
RequireMergeableFlag: true,
SilenceNoProjectsFlag: false,
SilenceForkPRErrorsFlag: true,
SilenceAllowlistErrorsFlag: true,
SilenceVCSStatusNoPlans: true,
SkipCloneNoChanges: true,
SlackTokenFlag: "slack-token",
SSLCertFileFlag: "cert-file",
SSLKeyFileFlag: "key-file",
TFDownloadURLFlag: "https://my-hostname.com",
TFEHostnameFlag: "my-hostname",
TFELocalExecutionModeFlag: true,
TFETokenFlag: "my-token",
VCSStatusName: "my-status",
WriteGitCredsFlag: true,
DisableAutoplanFlag: true,
EnablePolicyChecksFlag: false,
EnableRegExpCmdFlag: false,
EnableDiffMarkdownFormat: false,
ADTokenFlag: "ad-token",
ADUserFlag: "ad-user",
ADWebhookPasswordFlag: "ad-wh-pass",
ADWebhookUserFlag: "ad-wh-user",
AtlantisURLFlag: "url",
AllowForkPRsFlag: true,
AllowRepoConfigFlag: true,
AutomergeFlag: true,
AutoplanFileListFlag: "**/*.tf,**/*.yml",
BitbucketBaseURLFlag: "https://bitbucket-base-url.com",
BitbucketTokenFlag: "bitbucket-token",
BitbucketUserFlag: "bitbucket-user",
BitbucketWebhookSecretFlag: "bitbucket-secret",
CheckoutStrategyFlag: "merge",
DataDirFlag: "/path",
DefaultTFVersionFlag: "v0.11.0",
DisableApplyAllFlag: true,
DisableApplyFlag: true,
DisableMarkdownFoldingFlag: true,
DisableRepoLockingFlag: true,
GHHostnameFlag: "ghhostname",
GHTokenFlag: "token",
GHUserFlag: "user",
GHAppIDFlag: int64(0),
GHAppKeyFlag: "",
GHAppKeyFileFlag: "",
GHAppSlugFlag: "atlantis",
GHOrganizationFlag: "",
GHWebhookSecretFlag: "secret",
GitlabHostnameFlag: "gitlab-hostname",
GitlabTokenFlag: "gitlab-token",
GitlabUserFlag: "gitlab-user",
GitlabWebhookSecretFlag: "gitlab-secret",
LockingDBType: "boltdb",
LogLevelFlag: "debug",
MarkdownTemplateOverridesDirFlag: "/path2",
StatsNamespace: "atlantis",
AllowDraftPRs: true,
PortFlag: 8181,
ParallelPoolSize: 100,
RepoAllowlistFlag: "github.com/runatlantis/atlantis",
RequireApprovalFlag: true,
RequireMergeableFlag: true,
SilenceNoProjectsFlag: false,
SilenceForkPRErrorsFlag: true,
SilenceAllowlistErrorsFlag: true,
SilenceVCSStatusNoPlans: true,
SkipCloneNoChanges: true,
SlackTokenFlag: "slack-token",
SSLCertFileFlag: "cert-file",
SSLKeyFileFlag: "key-file",
TFDownloadURLFlag: "https://my-hostname.com",
TFEHostnameFlag: "my-hostname",
TFELocalExecutionModeFlag: true,
TFETokenFlag: "my-token",
VCSStatusName: "my-status",
WriteGitCredsFlag: true,
DisableAutoplanFlag: true,
EnablePolicyChecksFlag: false,
EnableRegExpCmdFlag: false,
EnableDiffMarkdownFormat: false,
}
func TestExecute_Defaults(t *testing.T) {
@@ -128,17 +129,20 @@ func TestExecute_Defaults(t *testing.T) {
hostname, err := os.Hostname()
Ok(t, err)
// Get our home dir since that's what data-dir defaulted to.
// Get our home dir since that's what data-dir and markdown-template-overrides-dir defaulted to.
dataDir, err := homedir.Expand("~/.atlantis")
Ok(t, err)
markdownTemplateOverridesDir, err := homedir.Expand("~/.markdown_templates")
Ok(t, err)
strExceptions := map[string]string{
GHUserFlag: "user",
GHTokenFlag: "token",
DataDirFlag: dataDir,
AtlantisURLFlag: "http://" + hostname + ":4141",
RepoAllowlistFlag: "*",
VarFileAllowlistFlag: dataDir,
GHUserFlag: "user",
GHTokenFlag: "token",
DataDirFlag: dataDir,
MarkdownTemplateOverridesDirFlag: markdownTemplateOverridesDir,
AtlantisURLFlag: "http://" + hostname + ":4141",
RepoAllowlistFlag: "*",
VarFileAllowlistFlag: dataDir,
}
strIgnore := map[string]bool{
"config": true,

View File

@@ -456,6 +456,21 @@ Values are chosen in this order:
```
Log level. Defaults to `info`.
### `--markdown-template-overrides-dir`
```bash
atlantis server --markdown-template-overrides-dir="path/to/templates/"
```
Directory where Atlantis will read in overrides for markdown templates used to render comments on pull requests.
Markdown template overrides may be specified either in individual files, or all together in a single file. All template
override files _must_ have the `.tmpl` extension, otherwise they will not be parsed.
Markdown templates which may have overrides can be found [here](https://github.com/runatlantis/atlantis/tree/master/server/events/templates)
Please be mindful that settings like `--enable-diff-markdown-format` depend on logic defined in the templates. It is
possible to diverge from expected behavior, if care is not taken when overriding default templates.
Defaults to the atlantis home directory `/home/atlantis/.markdown_templates/` in `/$HOME/.markdown_templates`.
### `--parallel-pool-size`
```bash
atlantis server --parallel-pool-size=100

View File

@@ -1036,7 +1036,7 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl
pullUpdater := &events.PullUpdater{
HidePrevPlanComments: false,
VCSClient: e2eVCSClient,
MarkdownRenderer: &events.MarkdownRenderer{},
MarkdownRenderer: events.GetMarkdownRenderer(false, false, false, false, false, false, ""),
}
autoMerger := &events.AutoMerger{

View File

@@ -99,7 +99,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
pullUpdater = &events.PullUpdater{
HidePrevPlanComments: false,
VCSClient: vcsClient,
MarkdownRenderer: &events.MarkdownRenderer{},
MarkdownRenderer: events.GetMarkdownRenderer(false, false, false, false, false, false, ""),
}
autoMerger = &events.AutoMerger{

View File

@@ -15,6 +15,7 @@ package events
import (
"bytes"
"embed"
"fmt"
"strings"
"text/template"
@@ -33,6 +34,9 @@ var (
// maxUnwrappedLines is the maximum number of lines the Terraform output
// can be before we wrap it in an expandable template.
maxUnwrappedLines = 12
//go:embed templates/*
templatesFS embed.FS
)
// MarkdownRenderer renders responses as markdown.
@@ -46,6 +50,7 @@ type MarkdownRenderer struct {
DisableMarkdownFolding bool
DisableRepoLocking bool
EnableDiffMarkdownFormat bool
MarkdownTemplates *template.Template
}
// commonData is data that all responses have.
@@ -98,6 +103,33 @@ type projectResultTmplData struct {
Rendered string
}
// Initialize templates
func GetMarkdownRenderer(
GitlabSupportsCommonMark bool,
DisableApplyAll bool,
DisableApply bool,
DisableMarkdownFolding bool,
DisableRepoLocking bool,
EnableDiffMarkdownFormat bool,
MarkdownTemplateOverridesDir string,
) *MarkdownRenderer {
var templates *template.Template
templates, _ = template.New("").Funcs(sprig.TxtFuncMap()).ParseFS(templatesFS, "templates/*.tmpl")
if overrides, err := templates.ParseGlob(fmt.Sprintf("%s/*.tmpl", MarkdownTemplateOverridesDir)); err == nil {
// doesn't override if templates directory doesn't exist
templates = overrides
}
return &MarkdownRenderer{
GitlabSupportsCommonMark: GitlabSupportsCommonMark,
DisableApplyAll: DisableApplyAll,
DisableMarkdownFolding: DisableMarkdownFolding,
DisableApply: DisableApply,
DisableRepoLocking: DisableRepoLocking,
EnableDiffMarkdownFormat: EnableDiffMarkdownFormat,
MarkdownTemplates: templates,
}
}
// Render formats the data into a markdown string.
// nolint: interfacer
func (m *MarkdownRenderer) Render(res command.Result, cmdName command.Name, log string, verbose bool, vcsHost models.VCSHostType) string {
@@ -112,11 +144,14 @@ func (m *MarkdownRenderer) Render(res command.Result, cmdName command.Name, log
DisableRepoLocking: m.DisableRepoLocking,
EnableDiffMarkdownFormat: m.EnableDiffMarkdownFormat,
}
templates := m.MarkdownTemplates
if res.Error != nil {
return m.renderTemplate(unwrappedErrWithLogTmpl, errData{res.Error.Error(), common})
return m.renderTemplate(templates.Lookup("unwrappedErrWithLog"), errData{res.Error.Error(), common})
}
if res.Failure != "" {
return m.renderTemplate(failureWithLogTmpl, failureData{res.Failure, common})
return m.renderTemplate(templates.Lookup("failureWithLog"), failureData{res.Failure, common})
}
return m.renderProjectResults(res.ProjectResults, common, vcsHost)
}
@@ -127,6 +162,8 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
numPolicyCheckSuccesses := 0
numVersionSuccesses := 0
templates := m.MarkdownTemplates
for _, result := range results {
resultData := projectResultTmplData{
Workspace: result.Workspace,
@@ -134,9 +171,9 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
ProjectName: result.ProjectName,
}
if result.Error != nil {
tmpl := unwrappedErrTmpl
tmpl := templates.Lookup("unwrappedErr")
if m.shouldUseWrappedTmpl(vcsHost, result.Error.Error()) {
tmpl = wrappedErrTmpl
tmpl = templates.Lookup("wrappedErr")
}
resultData.Rendered = m.renderTemplate(tmpl, struct {
Command string
@@ -146,7 +183,7 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
Error: result.Error.Error(),
})
} else if result.Failure != "" {
resultData.Rendered = m.renderTemplate(failureTmpl, struct {
resultData.Rendered = m.renderTemplate(templates.Lookup("failure"), struct {
Command string
Failure string
}{
@@ -155,29 +192,29 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
})
} else if result.PlanSuccess != nil {
if m.shouldUseWrappedTmpl(vcsHost, result.PlanSuccess.TerraformOutput) {
resultData.Rendered = m.renderTemplate(planSuccessWrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanSummary: result.PlanSuccess.Summary(), PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
resultData.Rendered = m.renderTemplate(templates.Lookup("planSuccessWrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanSummary: result.PlanSuccess.Summary(), PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
} else {
resultData.Rendered = m.renderTemplate(planSuccessUnwrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
resultData.Rendered = m.renderTemplate(templates.Lookup("planSuccessUnwrapped"), planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply, DisableRepoLocking: common.DisableRepoLocking, EnableDiffMarkdownFormat: common.EnableDiffMarkdownFormat})
}
numPlanSuccesses++
} else if result.PolicyCheckSuccess != nil {
if m.shouldUseWrappedTmpl(vcsHost, result.PolicyCheckSuccess.PolicyCheckOutput) {
resultData.Rendered = m.renderTemplate(policyCheckSuccessWrappedTmpl, policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("policyCheckSuccessWrapped"), policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess})
} else {
resultData.Rendered = m.renderTemplate(policyCheckSuccessUnwrappedTmpl, policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("policyCheckSuccessUnwrapped"), policyCheckSuccessData{PolicyCheckSuccess: *result.PolicyCheckSuccess})
}
numPolicyCheckSuccesses++
} else if result.ApplySuccess != "" {
if m.shouldUseWrappedTmpl(vcsHost, result.ApplySuccess) {
resultData.Rendered = m.renderTemplate(applyWrappedSuccessTmpl, struct{ Output string }{result.ApplySuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("applyWrappedSuccess"), struct{ Output string }{result.ApplySuccess})
} else {
resultData.Rendered = m.renderTemplate(applyUnwrappedSuccessTmpl, struct{ Output string }{result.ApplySuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("applyUnwrappedSuccess"), struct{ Output string }{result.ApplySuccess})
}
} else if result.VersionSuccess != "" {
if m.shouldUseWrappedTmpl(vcsHost, result.VersionSuccess) {
resultData.Rendered = m.renderTemplate(versionWrappedSuccessTmpl, struct{ Output string }{result.VersionSuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("versionWrappedSuccess"), struct{ Output string }{result.VersionSuccess})
} else {
resultData.Rendered = m.renderTemplate(versionUnwrappedSuccessTmpl, struct{ Output string }{result.VersionSuccess})
resultData.Rendered = m.renderTemplate(templates.Lookup("versionUnwrappedSuccess"), struct{ Output string }{result.VersionSuccess})
}
numVersionSuccesses++
} else {
@@ -189,28 +226,28 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
var tmpl *template.Template
switch {
case len(resultsTmplData) == 1 && common.Command == planCommandTitle && numPlanSuccesses > 0:
tmpl = singleProjectPlanSuccessTmpl
tmpl = templates.Lookup("singleProjectPlanSuccess")
case len(resultsTmplData) == 1 && common.Command == planCommandTitle && numPlanSuccesses == 0:
tmpl = singleProjectPlanUnsuccessfulTmpl
tmpl = templates.Lookup("singleProjectPlanUnsuccessful")
case len(resultsTmplData) == 1 && common.Command == policyCheckCommandTitle && numPolicyCheckSuccesses > 0:
tmpl = singleProjectPlanSuccessTmpl
tmpl = templates.Lookup("singleProjectPlanSuccess")
case len(resultsTmplData) == 1 && common.Command == policyCheckCommandTitle && numPolicyCheckSuccesses == 0:
tmpl = singleProjectPlanUnsuccessfulTmpl
tmpl = templates.Lookup("singleProjectPlanUnsuccessful")
case len(resultsTmplData) == 1 && common.Command == versionCommandTitle && numVersionSuccesses > 0:
tmpl = singleProjectVersionSuccessTmpl
tmpl = templates.Lookup("singleProjectVersionSuccess")
case len(resultsTmplData) == 1 && common.Command == versionCommandTitle && numVersionSuccesses == 0:
tmpl = singleProjectVersionUnsuccessfulTmpl
tmpl = templates.Lookup("singleProjectVersionUnsuccessful")
case len(resultsTmplData) == 1 && common.Command == applyCommandTitle:
tmpl = singleProjectApplyTmpl
tmpl = templates.Lookup("singleProjectApply")
case common.Command == planCommandTitle,
common.Command == policyCheckCommandTitle:
tmpl = multiProjectPlanTmpl
tmpl = templates.Lookup("multiProjectPlan")
case common.Command == approvePoliciesCommandTitle:
tmpl = approveAllProjectsTmpl
tmpl = templates.Lookup("approveAllProjects")
case common.Command == applyCommandTitle:
tmpl = multiProjectApplyTmpl
tmpl = templates.Lookup("multiProjectApply")
case common.Command == versionCommandTitle:
tmpl = multiProjectVersionTmpl
tmpl = templates.Lookup("multiProjectVersion")
default:
return "no template matchedthis is a bug"
}
@@ -245,146 +282,3 @@ func (m *MarkdownRenderer) renderTemplate(tmpl *template.Template, data interfac
}
return buf.String()
}
// todo: refactor to remove duplication #refactor
var singleProjectApplyTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
var singleProjectPlanSuccessTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" +
"\n" +
"{{ if ne .DisableApplyAll true }}---\n" +
"* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n" +
" * `atlantis apply`\n" +
"* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:\n" +
" * `atlantis unlock`{{ end }}" + logTmpl))
var singleProjectPlanUnsuccessfulTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n" +
"{{$result.Rendered}}\n" + logTmpl))
var singleProjectVersionSuccessTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
var singleProjectVersionUnsuccessfulTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
var approveAllProjectsTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Approved Policies for {{ len .Results }} projects:\n\n" +
"{{ range $result := .Results }}" +
"1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{end}}\n" + logTmpl))
var multiProjectPlanTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
"{{ range $result := .Results }}" +
"1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{end}}\n" +
"{{ $disableApplyAll := .DisableApplyAll }}{{ range $i, $result := .Results }}" +
"### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{$result.Rendered}}\n\n" +
"{{ if ne $disableApplyAll true }}---\n{{end}}{{end}}{{ if ne .DisableApplyAll true }}{{ if and (gt (len .Results) 0) (not .PlansDeleted) }}* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n" +
" * `atlantis apply`\n" +
"* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:\n" +
" * `atlantis unlock`" +
"{{end}}{{end}}" +
logTmpl))
var multiProjectApplyTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
"{{ range $result := .Results }}" +
"1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{end}}\n" +
"{{ range $i, $result := .Results }}" +
"### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{$result.Rendered}}\n\n" +
"---\n{{end}}" +
logTmpl))
var multiProjectVersionTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
"{{ range $result := .Results }}" +
"1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{end}}\n" +
"{{ range $i, $result := .Results }}" +
"### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
"{{$result.Rendered}}\n\n" +
"---\n{{end}}" +
logTmpl))
var planSuccessUnwrappedTmpl = template.Must(template.New("").Parse(
"```diff\n" +
"{{ if .EnableDiffMarkdownFormat }}{{.DiffMarkdownFormattedTerraformOutput}}{{else}}{{.TerraformOutput}}{{end}}\n" +
"```\n\n" + planNextSteps +
"{{ if .HasDiverged }}\n\n:warning: The branch we're merging into is ahead, it is recommended to pull new commits first.{{end}}"))
var planSuccessWrappedTmpl = template.Must(template.New("").Parse(
"<details><summary>Show Output</summary>\n\n" +
"```diff\n" +
"{{ if .EnableDiffMarkdownFormat }}{{.DiffMarkdownFormattedTerraformOutput}}{{else}}{{.TerraformOutput}}{{end}}\n" +
"```\n\n" +
planNextSteps + "\n" +
"</details>" + "\n" +
"{{.PlanSummary}}" +
"{{ if .HasDiverged }}\n\n:warning: The branch we're merging into is ahead, it is recommended to pull new commits first.{{end}}"))
var policyCheckSuccessUnwrappedTmpl = template.Must(template.New("").Parse(
"```diff\n" +
"{{.PolicyCheckOutput}}\n" +
"```\n\n" + policyCheckNextSteps +
"{{ if .HasDiverged }}\n\n:warning: The branch we're merging into is ahead, it is recommended to pull new commits first.{{end}}"))
var policyCheckSuccessWrappedTmpl = template.Must(template.New("").Parse(
"<details><summary>Show Output</summary>\n\n" +
"```diff\n" +
"{{.PolicyCheckOutput}}\n" +
"```\n\n" +
policyCheckNextSteps + "\n" +
"</details>" +
"{{ if .HasDiverged }}\n\n:warning: The branch we're merging into is ahead, it is recommended to pull new commits first.{{end}}"))
// policyCheckNextSteps are instructions appended after successful plans as to what
// to do next.
var policyCheckNextSteps = "* :arrow_forward: To **apply** this plan, comment:\n" +
" * `{{.ApplyCmd}}`\n" +
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})\n" +
"* :repeat: To re-run policies **plan** this project again by commenting:\n" +
" * `{{.RePlanCmd}}`"
// planNextSteps are instructions appended after successful plans as to what
// to do next.
var planNextSteps = "{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}" +
"{{ if not .DisableApply }}* :arrow_forward: To **apply** this plan, comment:\n" +
" * `{{.ApplyCmd}}`\n{{end}}" +
"{{ if not .DisableRepoLocking }}* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})\n{{end}}" +
"* :repeat: To **plan** this project again, comment:\n" +
" * `{{.RePlanCmd}}`{{end}}"
var applyUnwrappedSuccessTmpl = template.Must(template.New("").Parse(
"```diff\n" +
"{{.Output}}\n" +
"```"))
var applyWrappedSuccessTmpl = template.Must(template.New("").Parse(
"<details><summary>Show Output</summary>\n\n" +
"```diff\n" +
"{{.Output}}\n" +
"```\n" +
"</details>"))
var versionUnwrappedSuccessTmpl = template.Must(template.New("").Parse("```\n{{.Output}}```"))
var versionWrappedSuccessTmpl = template.Must(template.New("").Parse(
"<details><summary>Show Output</summary>\n\n" +
"```\n" +
"{{.Output}}" +
"```\n" +
"</details>"))
var unwrappedErrTmplText = "**{{.Command}} Error**\n" +
"```\n" +
"{{.Error}}\n" +
"```" +
"{{ if eq .Command \"Policy Check\" }}" +
"\n* :heavy_check_mark: To **approve** failing policies an authorized approver can comment:\n" +
" * `atlantis approve_policies`\n" +
"* :repeat: Or, address the policy failure by modifying the codebase and re-planning.\n" +
"{{ end }}"
var wrappedErrTmplText = "**{{.Command}} Error**\n" +
"<details><summary>Show Output</summary>\n\n" +
"```\n" +
"{{.Error}}\n" +
"```\n</details>"
var unwrappedErrTmpl = template.Must(template.New("").Parse(unwrappedErrTmplText))
var unwrappedErrWithLogTmpl = template.Must(template.New("").Parse(unwrappedErrTmplText + logTmpl))
var wrappedErrTmpl = template.Must(template.New("").Parse(wrappedErrTmplText))
var failureTmplText = "**{{.Command}} Failed**: {{.Failure}}"
var failureTmpl = template.Must(template.New("").Parse(failureTmplText))
var failureWithLogTmpl = template.Must(template.New("").Parse(failureTmplText + logTmpl))
var logTmpl = "{{if .Verbose}}\n<details><summary>Log</summary>\n <p>\n\n```\n{{.Log}}```\n</p></details>{{end}}\n"

View File

@@ -16,6 +16,7 @@ package events_test
import (
"errors"
"fmt"
"os"
"strings"
"testing"
@@ -56,7 +57,7 @@ func TestRenderErr(t *testing.T) {
},
}
r := events.MarkdownRenderer{}
r := events.GetMarkdownRenderer(false, false, false, false, false, false, "")
for _, c := range cases {
res := command.Result{
Error: c.Error,
@@ -101,7 +102,7 @@ func TestRenderFailure(t *testing.T) {
},
}
r := events.MarkdownRenderer{}
r := events.GetMarkdownRenderer(false, false, false, false, false, false, "")
for _, c := range cases {
res := command.Result{
Failure: c.Failure,
@@ -120,7 +121,7 @@ func TestRenderFailure(t *testing.T) {
}
func TestRenderErrAndFailure(t *testing.T) {
r := events.MarkdownRenderer{}
r := events.GetMarkdownRenderer(false, false, false, false, false, false, "")
res := command.Result{
Error: errors.New("error"),
Failure: "failure",
@@ -750,7 +751,7 @@ $$$
},
}
r := events.MarkdownRenderer{}
r := events.GetMarkdownRenderer(false, false, false, false, false, false, "")
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
@@ -901,9 +902,15 @@ $$$
`,
},
}
r := events.MarkdownRenderer{
DisableApplyAll: true,
}
r := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
true, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
@@ -1046,10 +1053,16 @@ $$$
`,
},
}
r := events.MarkdownRenderer{
DisableApplyAll: true,
DisableApply: true,
}
r := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
true, // DisableApplyAll
true, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{
@@ -1070,11 +1083,55 @@ $$$
}
}
// Run policy check with a custom template to validate custom template rendering.
func TestRenderCustomPolicyCheckTemplate_DisableApplyAll(t *testing.T) {
tmpDir, cleanup := TempDir(t)
filePath := fmt.Sprintf("%s/templates.tmpl", tmpDir)
_, err := os.Create(filePath)
Ok(t, err)
err = os.WriteFile(filePath, []byte("{{ define \"policyCheckSuccessUnwrapped\" -}}somecustometext{{- end}}\n"), 0600)
Ok(t, err)
defer cleanup()
r := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
true, // DisableApplyAll
true, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
tmpDir, // MarkdownTemplateOverridesDir
)
rendered := r.Render(command.Result{
ProjectResults: []command.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PolicyCheckSuccess: &models.PolicyCheckSuccess{
PolicyCheckOutput: "4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
},
}, command.PolicyCheck, "log", false, models.Github)
fmt.Println(rendered)
Equals(t, rendered, "Ran Policy Check for dir: `path` workspace: `workspace`\n\nsomecustometext\n\n\n")
}
// Test that if folding is disabled that it's not used.
func TestRenderProjectResults_DisableFolding(t *testing.T) {
mr := events.MarkdownRenderer{
DisableMarkdownFolding: true,
}
mr := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
true, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
rendered := mr.Render(command.Result{
ProjectResults: []command.ProjectResult{
@@ -1156,9 +1213,15 @@ func TestRenderProjectResults_WrappedErr(t *testing.T) {
for _, c := range cases {
t.Run(fmt.Sprintf("%s_%v", c.VCSHost.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.MarkdownRenderer{
GitlabSupportsCommonMark: c.GitlabCommonMarkSupport,
}
mr := events.GetMarkdownRenderer(
c.GitlabCommonMarkSupport, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
rendered := mr.Render(command.Result{
ProjectResults: []command.ProjectResult{
@@ -1268,9 +1331,15 @@ func TestRenderProjectResults_WrapSingleProject(t *testing.T) {
for _, cmd := range []command.Name{command.Plan, command.Apply} {
t.Run(fmt.Sprintf("%s_%s_%v", c.VCSHost.String(), cmd.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.MarkdownRenderer{
GitlabSupportsCommonMark: c.GitlabCommonMarkSupport,
}
mr := events.GetMarkdownRenderer(
c.GitlabCommonMarkSupport, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
var pr command.ProjectResult
switch cmd {
case command.Plan:
@@ -1373,7 +1442,15 @@ $$$
}
func TestRenderProjectResults_MultiProjectApplyWrapped(t *testing.T) {
mr := events.MarkdownRenderer{}
mr := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
tfOut := strings.Repeat("line\n", 13)
rendered := mr.Render(command.Result{
ProjectResults: []command.ProjectResult{
@@ -1419,7 +1496,15 @@ $$$
}
func TestRenderProjectResults_MultiProjectPlanWrapped(t *testing.T) {
mr := events.MarkdownRenderer{}
mr := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
tfOut := strings.Repeat("line\n", 13) + "Plan: 1 to add, 0 to change, 0 to destroy."
rendered := mr.Render(command.Result{
ProjectResults: []command.ProjectResult{
@@ -1592,7 +1677,15 @@ This plan was not saved because one or more projects failed and automerge requir
for name, c := range cases {
t.Run(name, func(t *testing.T) {
mr := events.MarkdownRenderer{}
mr := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
rendered := mr.Render(c.cr, command.Plan, "log", false, models.Github)
expWithBackticks := strings.Replace(c.exp, "$", "`", -1)
Equals(t, expWithBackticks, rendered)
@@ -2052,7 +2145,15 @@ $$$
},
}
r := events.MarkdownRenderer{}
r := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
false, // DisableApplyAll
false, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
false, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
r.DisableRepoLocking = true
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
@@ -2481,11 +2582,16 @@ Plan: 1 to add, 2 to change, 1 to destroy.
`,
},
}
r := events.MarkdownRenderer{
DisableApplyAll: true,
DisableApply: true,
EnableDiffMarkdownFormat: true,
}
r := events.GetMarkdownRenderer(
false, // GitlabSupportsCommonMark
true, // DisableApplyAll
true, // DisableApply
false, // DisableMarkdownFolding
false, // DisableRepoLocking
true, // EnableDiffMarkdownFormat
"", // MarkdownTemplateOverridesDir
)
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := command.Result{

View File

@@ -0,0 +1,5 @@
{{ define "applyUnwrappedSuccess" -}}
```diff
{{.Output}}
```
{{- end }}

View File

@@ -0,0 +1,6 @@
{{ define "applyWrappedSuccess" -}}
<details><summary>Show Output</summary>
{{ template "applyUnwrappedSuccess" . }}
</details>
{{- end }}

View File

@@ -0,0 +1,7 @@
{{ define "approveAllProjects" -}}
Approved Policies for {{ len .Results }} projects:
{{ range $result := .Results }}1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{end}}
{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,5 @@
{{ define "diverged" -}}
{{ if .HasDiverged }}
:warning: The branch we're merging into is ahead, it is recommended to pull new commits first.{{end}}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ define "failure" -}}
**{{.Command}} Failed**: {{.Failure}}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ define "failureWithLog" -}}
{{ template "failure" . }}{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,9 @@
{{ define "log" -}}
{{if .Verbose}}
<details><summary>Log</summary>
<p>
```
{{.Log}}```
</p></details>{{end}}
{{- end }}

View File

@@ -0,0 +1,8 @@
{{ define "multiProjectApply" -}}
{{ template "multiProjectHeader" . }}
{{ range $i, $result := .Results }}### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
---
{{end}}{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "multiProjectHeader" -}}
Ran {{.Command}} for {{ len .Results }} projects:
{{ range $result := .Results }}1. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{end}}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{ define "multiProjectPlan" -}}
{{ template "multiProjectHeader" . }}
{{ $disableApplyAll := .DisableApplyAll }}{{ range $i, $result := .Results }}### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
{{ if ne $disableApplyAll true }}---
{{end}}{{end}}{{ if ne .DisableApplyAll true }}{{ if and (gt (len .Results) 0) (not .PlansDeleted) }}* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* `atlantis apply`
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
* `atlantis unlock`{{end}}{{end}}{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,3 @@
{{ define "multiProjectVersion" -}}
{{ template "multiProjectApply" . }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{ define "planSuccessUnwrapped" -}}
```diff
{{ if .EnableDiffMarkdownFormat }}{{.DiffMarkdownFormattedTerraformOutput}}{{else}}{{.TerraformOutput}}{{end}}
```
{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}{{ if not .DisableApply }}* :arrow_forward: To **apply** this plan, comment:
* `{{.ApplyCmd}}`
{{end}}{{ if not .DisableRepoLocking }}* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})
{{end}}* :repeat: To **plan** this project again, comment:
* `{{.RePlanCmd}}`{{end}}{{ template "diverged" . }}
{{- end }}

View File

@@ -0,0 +1,15 @@
{{ define "planSuccessWrapped" -}}
<details><summary>Show Output</summary>
```diff
{{ if .EnableDiffMarkdownFormat }}{{.DiffMarkdownFormattedTerraformOutput}}{{else}}{{.TerraformOutput}}{{end}}
```
{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}{{ if not .DisableApply }}* :arrow_forward: To **apply** this plan, comment:
* `{{.ApplyCmd}}`
{{end}}{{ if not .DisableRepoLocking }}* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})
{{end}}* :repeat: To **plan** this project again, comment:
* `{{.RePlanCmd}}`{{end}}
</details>
{{.PlanSummary}}{{ template "diverged" . }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{ define "policyCheckSuccessUnwrapped" -}}
```diff
{{.PolicyCheckOutput}}
```
* :arrow_forward: To **apply** this plan, comment:
* `{{.ApplyCmd}}`
* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})
* :repeat: To re-run policies **plan** this project again by commenting:
* `{{.RePlanCmd}}`{{ template "diverged" . }}
{{- end }}

View File

@@ -0,0 +1,14 @@
{{ define "policyCheckSuccessWrapped" -}}
<details><summary>Show Output</summary>
```diff
{{.PolicyCheckOutput}}
```
* :arrow_forward: To **apply** this plan, comment:
* `{{.ApplyCmd}}`
* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})
* :repeat: To re-run policies **plan** this project again by commenting:
* `{{.RePlanCmd}}`
</details>{{ template "diverged" . }}
{{- end }}

View File

@@ -0,0 +1,6 @@
{{ define "singleProjectApply" -}}
{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,11 @@
{{ define "singleProjectPlanSuccess" -}}
{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
{{ if ne .DisableApplyAll true }}---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* `atlantis apply`
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
* `atlantis unlock`{{ end }}{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "singleProjectPlanUnsuccessful" -}}
{{$result := index .Results 0}}Ran {{.Command}} for dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "singleProjectVersionSuccess" -}}
{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
{{$result.Rendered}}
{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,3 @@
{{ define "singleProjectVersionUnsuccessful" -}}
{{ template "singleProjectPlanUnsuccessful" . }}
{{- end }}

View File

@@ -0,0 +1,10 @@
{{ define "unwrappedErr" -}}
**{{.Command}} Error**
```
{{.Error}}
```{{ if eq .Command "Policy Check" }}
* :heavy_check_mark: To **approve** failing policies an authorized approver can comment:
* `atlantis approve_policies`
* :repeat: Or, address the policy failure by modifying the codebase and re-planning.
{{ end }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ define "unwrappedErrWithLog" -}}
{{ template "unwrappedErr" . }}{{ template "log" . }}
{{ end }}

View File

@@ -0,0 +1,5 @@
{{ define "versionUnwrappedSuccess" -}}
```
{{.Output}}
```
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "versionWrappedSuccess" -}}
<details><summary>Show Output</summary>
{{ template "versionUnwrappedSuccess" . }}
</details>
{{- end }}

View File

@@ -0,0 +1,9 @@
{{ define "wrappedErr" -}}
**{{.Command}} Error**
<details><summary>Show Output</summary>
```
{{.Error}}
```
</details>
{{- end }}

View File

@@ -389,14 +389,15 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
if err != nil && flag.Lookup("test.v") == nil {
return nil, errors.Wrap(err, "initializing terraform")
}
markdownRenderer := &events.MarkdownRenderer{
GitlabSupportsCommonMark: gitlabClient.SupportsCommonMark(),
DisableApplyAll: userConfig.DisableApplyAll,
DisableMarkdownFolding: userConfig.DisableMarkdownFolding,
DisableApply: userConfig.DisableApply,
DisableRepoLocking: userConfig.DisableRepoLocking,
EnableDiffMarkdownFormat: userConfig.EnableDiffMarkdownFormat,
}
markdownRenderer := events.GetMarkdownRenderer(
gitlabClient.SupportsCommonMark(),
userConfig.DisableApplyAll,
userConfig.DisableMarkdownFolding,
userConfig.DisableApply,
userConfig.DisableRepoLocking,
userConfig.EnableDiffMarkdownFormat,
userConfig.MarkdownTemplateOverridesDir,
)
var lockingClient locking.Locker
var applyLockingClient locking.ApplyLocker

View File

@@ -51,6 +51,7 @@ type UserConfig struct {
HidePrevPlanComments bool `mapstructure:"hide-prev-plan-comments"`
LockingDBType string `mapstructure:"locking-db-type"`
LogLevel string `mapstructure:"log-level"`
MarkdownTemplateOverridesDir string `mapstructure:"markdown-template-overrides-dir"`
ParallelPoolSize int `mapstructure:"parallel-pool-size"`
StatsNamespace string `mapstructure:"stats-namespace"`
PlanDrafts bool `mapstructure:"allow-draft-prs"`