Skip to content

Commit

Permalink
Add custom templating support (runatlantis#2647)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and krrrr38 committed Dec 16, 2022
1 parent ce4ec34 commit 2002d8f
Show file tree
Hide file tree
Showing 34 changed files with 593 additions and 354 deletions.
198 changes: 117 additions & 81 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down
138 changes: 71 additions & 67 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 2002d8f

Please sign in to comment.