diff --git a/cmd/server.go b/cmd/server.go index 08648cc809..a391516dcb 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -66,6 +66,7 @@ const ( EnablePolicyChecksFlag = "enable-policy-checks" EnableRegExpCmdFlag = "enable-regexp-cmd" EnableDiffMarkdownFormat = "enable-diff-markdown-format" + ExecutableName = "executable-name" GHHostnameFlag = "gh-hostname" GHTeamAllowlistFlag = "gh-team-allowlist" GHTokenFlag = "gh-token" @@ -135,6 +136,7 @@ const ( DefaultCheckoutStrategy = "branch" DefaultBitbucketBaseURL = bitbucketcloud.BaseURL DefaultDataDir = "~/.atlantis" + DefaultExecutableName = "atlantis" DefaultMarkdownTemplateOverridesDir = "~/.markdown_templates" DefaultGHHostname = "github.com" DefaultGitlabHostname = "gitlab.com" @@ -229,6 +231,10 @@ var stringFlags = map[string]stringFlag{ description: "Path to directory to store Atlantis data.", defaultValue: DefaultDataDir, }, + ExecutableName: { + description: "Comment command executable name.", + defaultValue: DefaultExecutableName, + }, GHHostnameFlag: { description: "Hostname of your Github Enterprise installation. If using github.com, no need to set.", defaultValue: DefaultGHHostname, @@ -749,6 +755,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) { if c.BitbucketBaseURL == "" { c.BitbucketBaseURL = DefaultBitbucketBaseURL } + if c.ExecutableName == "" { + c.ExecutableName = DefaultExecutableName + } if c.LockingDBType == "" { c.LockingDBType = DefaultLockingDBType } diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index d3d90d8e6d..2af2e8b188 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -395,6 +395,16 @@ and set `--autoplan-modules` to `false`. Useful to enable for use with GitHub. +### `--executable-name` + ```bash + atlantis server --executable-name="atlantis" + # or + ATLANTIS_EXECUTABLE_NAME="atlantis" + ``` + Comment command trigger executable name. Defaults to `atlantis`. + + This is useful when running multiple Atlantis servers against a single repository. + ### `--gh-hostname` ```bash atlantis server --gh-hostname="my.github.enterprise.com" diff --git a/server/controllers/events/events_controller_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go index a5236d337c..126e82e9e4 100644 --- a/server/controllers/events/events_controller_e2e_test.go +++ b/server/controllers/events/events_controller_e2e_test.go @@ -890,8 +890,9 @@ func setupE2E(t *testing.T, repoDir string) (events_controllers.VCSEventsControl GitlabToken: "gitlab-token", } commentParser := &events.CommentParser{ - GithubUser: "github-user", - GitlabUser: "gitlab-user", + GithubUser: "github-user", + GitlabUser: "gitlab-user", + ExecutableName: "atlantis", } terraformClient, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", &NoopTFDownloader{}, false, projectCmdOutputHandler) Ok(t, err) diff --git a/server/controllers/events/events_controller_test.go b/server/controllers/events/events_controller_test.go index c974da309c..689069dcd0 100644 --- a/server/controllers/events/events_controller_test.go +++ b/server/controllers/events/events_controller_test.go @@ -199,7 +199,7 @@ func TestPost_GitlabCommentNotAllowlisted(t *testing.T) { e := events_controllers.VCSEventsController{ Logger: logger, Scope: scope, - CommentParser: &events.CommentParser{}, + CommentParser: &events.CommentParser{ExecutableName: "atlantis"}, GitlabRequestParserValidator: &events_controllers.DefaultGitlabRequestParserValidator{}, Parser: &events.EventParser{}, SupportedVCSHosts: []models.VCSHostType{models.Gitlab}, @@ -230,7 +230,7 @@ func TestPost_GitlabCommentNotAllowlistedWithSilenceErrors(t *testing.T) { e := events_controllers.VCSEventsController{ Logger: logger, Scope: scope, - CommentParser: &events.CommentParser{}, + CommentParser: &events.CommentParser{ExecutableName: "atlantis"}, GitlabRequestParserValidator: &events_controllers.DefaultGitlabRequestParserValidator{}, Parser: &events.EventParser{}, SupportedVCSHosts: []models.VCSHostType{models.Gitlab}, @@ -263,7 +263,7 @@ func TestPost_GithubCommentNotAllowlisted(t *testing.T) { Logger: logger, Scope: scope, GithubRequestValidator: &events_controllers.DefaultGithubRequestValidator{}, - CommentParser: &events.CommentParser{}, + CommentParser: &events.CommentParser{ExecutableName: "atlantis"}, Parser: &events.EventParser{}, SupportedVCSHosts: []models.VCSHostType{models.Github}, RepoAllowlistChecker: &events.RepoAllowlistChecker{}, @@ -295,7 +295,7 @@ func TestPost_GithubCommentNotAllowlistedWithSilenceErrors(t *testing.T) { Logger: logger, Scope: scope, GithubRequestValidator: &events_controllers.DefaultGithubRequestValidator{}, - CommentParser: &events.CommentParser{}, + CommentParser: &events.CommentParser{ExecutableName: "atlantis"}, Parser: &events.EventParser{}, SupportedVCSHosts: []models.VCSHostType{models.Github}, RepoAllowlistChecker: &events.RepoAllowlistChecker{}, diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go index 48af12d7bd..0c8928a35b 100644 --- a/server/events/comment_parser.go +++ b/server/events/comment_parser.go @@ -41,7 +41,6 @@ const ( autoMergeDisabledFlagShort = "" verboseFlagLong = "verbose" verboseFlagShort = "" - atlantisExecutable = "atlantis" ) // multiLineRegex is used to ignore multi-line comments since those aren't valid @@ -79,6 +78,7 @@ type CommentParser struct { BitbucketUser string AzureDevopsUser string ApplyDisabled bool + ExecutableName string } // CommentParseResult describes the result of parsing a comment as a command. @@ -144,7 +144,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com case models.AzureDevops: vcsUser = e.AzureDevopsUser } - executableNames := []string{"run", atlantisExecutable, "@" + vcsUser} + executableNames := []string{"run", e.ExecutableName, "@" + vcsUser} if !e.stringInSlice(args[0], executableNames) { return CommentParseResult{Ignore: true} } @@ -290,19 +290,19 @@ func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, pr } commentFlags = fmt.Sprintf(" -- %s", strings.Join(flagsWithoutQuotes, " ")) } - return fmt.Sprintf("%s %s%s%s", atlantisExecutable, command.Plan.String(), flags, commentFlags) + return fmt.Sprintf("%s %s%s%s", e.ExecutableName, command.Plan.String(), flags, commentFlags) } // BuildApplyComment builds an apply comment for the specified args. func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string { flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled) - return fmt.Sprintf("%s %s%s", atlantisExecutable, command.Apply.String(), flags) + return fmt.Sprintf("%s %s%s", e.ExecutableName, command.Apply.String(), flags) } // BuildVersionComment builds a version comment for the specified args. func (e *CommentParser) BuildVersionComment(repoRelDir string, workspace string, project string) string { flags := e.buildFlags(repoRelDir, workspace, project, false) - return fmt.Sprintf("%s %s%s", atlantisExecutable, command.Version.String(), flags) + return fmt.Sprintf("%s %s%s", e.ExecutableName, command.Version.String(), flags) } func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string { diff --git a/server/events/comment_parser_test.go b/server/events/comment_parser_test.go index c19c404d34..aec505a1da 100644 --- a/server/events/comment_parser_test.go +++ b/server/events/comment_parser_test.go @@ -25,8 +25,9 @@ import ( ) var commentParser = events.CommentParser{ - GithubUser: "github-user", - GitlabUser: "gitlab-user", + GithubUser: "github-user", + GitlabUser: "gitlab-user", + ExecutableName: "atlantis", } func TestParse_Ignored(t *testing.T) { @@ -44,6 +45,30 @@ func TestParse_Ignored(t *testing.T) { } } +func TestParse_ExecutableName(t *testing.T) { + cases := []struct { + user string + expIgnore bool + }{ + {"custom-executable-name", false}, + {"run", false}, + {"@github-user", false}, + {"github-user", true}, + {"atlantis", true}, + } + for _, c := range cases { + t.Run(c.user, func(t *testing.T) { + var commentParser = events.CommentParser{ + GithubUser: "github-user", + ExecutableName: "custom-executable-name", + } + comment := fmt.Sprintf("%s help", c.user) + r := commentParser.Parse(comment, models.Github) + Assert(t, r.Ignore == c.expIgnore, "expected Ignore %q, but got %q", c.expIgnore, r.Ignore) + }) + } +} + func TestParse_HelpResponse(t *testing.T) { helpComments := []string{ "run", @@ -789,6 +814,7 @@ func TestParse_VCSUsername(t *testing.T) { GitlabUser: "gl", BitbucketUser: "bb", AzureDevopsUser: "ad", + ExecutableName: "atlantis", } cases := []struct { vcs models.VCSHostType diff --git a/server/events/project_command_builder_internal_test.go b/server/events/project_command_builder_internal_test.go index 3a9adbb9a3..62239521d9 100644 --- a/server/events/project_command_builder_internal_test.go +++ b/server/events/project_command_builder_internal_test.go @@ -630,7 +630,7 @@ projects: NewDefaultWorkingDirLocker(), globalCfg, &DefaultPendingPlanFinder{}, - &CommentParser{}, + &CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -834,7 +834,7 @@ projects: NewDefaultWorkingDirLocker(), globalCfg, &DefaultPendingPlanFinder{}, - &CommentParser{}, + &CommentParser{ExecutableName: "atlantis"}, false, true, "", @@ -1067,7 +1067,7 @@ workflows: NewDefaultWorkingDirLocker(), globalCfg, &DefaultPendingPlanFinder{}, - &CommentParser{}, + &CommentParser{ExecutableName: "atlantis"}, false, false, "", diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index b29c630938..2e9438fabd 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -155,7 +155,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -424,7 +424,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, true, "", @@ -597,7 +597,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, true, "", @@ -767,7 +767,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -859,7 +859,7 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) { events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -945,7 +945,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -1025,7 +1025,7 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) { events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -1230,7 +1230,7 @@ projects: events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -1320,7 +1320,7 @@ parallel_plan: true`, events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, true, false, "", @@ -1380,7 +1380,7 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman events.NewDefaultWorkingDirLocker(), globalCfg, &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", @@ -1463,7 +1463,7 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { events.NewDefaultWorkingDirLocker(), valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, - &events.CommentParser{}, + &events.CommentParser{ExecutableName: "atlantis"}, false, false, "", diff --git a/server/server.go b/server/server.go index 579a921597..d79eb64d70 100644 --- a/server/server.go +++ b/server/server.go @@ -502,6 +502,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { BitbucketUser: userConfig.BitbucketUser, AzureDevopsUser: userConfig.AzureDevopsUser, ApplyDisabled: userConfig.DisableApply, + ExecutableName: userConfig.ExecutableName, } defaultTfVersion := terraformClient.DefaultVersion() pendingPlanFinder := &events.DefaultPendingPlanFinder{} diff --git a/server/user_config.go b/server/user_config.go index 3b379455a5..f45b03dfa1 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -34,6 +34,7 @@ type UserConfig struct { EnablePolicyChecksFlag bool `mapstructure:"enable-policy-checks"` EnableRegExpCmd bool `mapstructure:"enable-regexp-cmd"` EnableDiffMarkdownFormat bool `mapstructure:"enable-diff-markdown-format"` + ExecutableName string `mapstructure:"executable-name"` GithubAllowMergeableBypassApply bool `mapstructure:"gh-allow-mergeable-bypass-apply"` GithubHostname string `mapstructure:"gh-hostname"` GithubToken string `mapstructure:"gh-token"`