diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go index 58b018878f..87f073ca15 100644 --- a/server/events/comment_parser.go +++ b/server/events/comment_parser.go @@ -16,15 +16,14 @@ package events import ( "fmt" "github.com/flynn-archive/go-shlex" + "github.com/runatlantis/atlantis/server/events/models" + "github.com/runatlantis/atlantis/server/events/yaml" + "github.com/spf13/pflag" "io/ioutil" "net/url" "path/filepath" "regexp" "strings" - - "github.com/runatlantis/atlantis/server/events/models" - "github.com/runatlantis/atlantis/server/events/yaml" - "github.com/spf13/pflag" ) const ( @@ -65,10 +64,9 @@ type CommentBuilder interface { // CommentParser implements CommentParsing type CommentParser struct { - GithubUser string - GithubToken string - GitlabUser string - GitlabToken string + GithubUser string + GitlabUser string + BitbucketUser string } // CommentParseResult describes the result of parsing a comment as a command. @@ -104,10 +102,9 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen return CommentParseResult{Ignore: true} } - args, err := shlex.Split(comment) - if err != nil { - return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError parsing command: %s\n```", err)} - } + // We first use strings.Fields to parse and do an initial evaluation. + // Later we use a proper shell parser and re-parse. + args := strings.Fields(comment) if len(args) < 1 { return CommentParseResult{Ignore: true} } @@ -119,18 +116,30 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen // Atlantis can be invoked using the name of the VCS host user we're // running under. Need to be able to match against that user. - vcsUser := e.GithubUser - if vcsHost == models.Gitlab { + var vcsUser string + switch vcsHost { + case models.Github: + vcsUser = e.GithubUser + case models.Gitlab: vcsUser = e.GitlabUser + case models.BitbucketCloud, models.BitbucketServer: + vcsUser = e.BitbucketUser } executableNames := []string{"run", atlantisExecutable, "@" + vcsUser} - - // If the comment doesn't start with the name of our 'executable' then - // ignore it. if !e.stringInSlice(args[0], executableNames) { return CommentParseResult{Ignore: true} } + // Now that we know Atlantis is being invoked, re-parse using a shell-style + // parser. + args, err := shlex.Split(comment) + if err != nil { + return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError parsing command: %s\n```", err)} + } + if len(args) < 1 { + return CommentParseResult{Ignore: true} + } + // If they've just typed the name of the executable then give them the help // output. if len(args) == 1 { diff --git a/server/events/comment_parser_test.go b/server/events/comment_parser_test.go index 4913a7fbc5..460b3c2dda 100644 --- a/server/events/comment_parser_test.go +++ b/server/events/comment_parser_test.go @@ -24,10 +24,8 @@ import ( ) var commentParser = events.CommentParser{ - GithubUser: "github-user", - GithubToken: "github-token", - GitlabUser: "gitlab-user", - GitlabToken: "gitlab-token", + GithubUser: "github-user", + GitlabUser: "gitlab-user", } func TestParse_Ignored(t *testing.T) { @@ -37,6 +35,7 @@ func TestParse_Ignored(t *testing.T) { "abc", "atlantis plan\nbut with newlines", "terraform plan\nbut with newlines", + "This shouldn't error, but it does.", } for _, c := range ignoreComments { r := commentParser.Parse(c, models.Github) @@ -643,6 +642,42 @@ func TestBuildPlanApplyComment(t *testing.T) { } } +func TestParse_VCSUsername(t *testing.T) { + cp := events.CommentParser{ + GithubUser: "gh", + GitlabUser: "gl", + BitbucketUser: "bb", + } + cases := []struct { + vcs models.VCSHostType + user string + }{ + { + vcs: models.Github, + user: "gh", + }, + { + vcs: models.Gitlab, + user: "gl", + }, + { + vcs: models.BitbucketServer, + user: "bb", + }, + { + vcs: models.BitbucketCloud, + user: "bb", + }, + } + + for _, c := range cases { + t.Run(c.vcs.String(), func(t *testing.T) { + r := cp.Parse(fmt.Sprintf("@%s %s", c.user, "help"), c.vcs) + Equals(t, events.HelpComment, r.CommentResponse) + }) + } +} + var PlanUsage = `Usage of plan: -d, --dir string Which directory to run plan in relative to root of repo, ex. 'child/dir'. diff --git a/server/events_controller_e2e_test.go b/server/events_controller_e2e_test.go index b1c78d49f7..df42eb3d95 100644 --- a/server/events_controller_e2e_test.go +++ b/server/events_controller_e2e_test.go @@ -368,10 +368,8 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClient, *moc GitlabToken: "gitlab-token", } commentParser := &events.CommentParser{ - GithubUser: "github-user", - GithubToken: "github-token", - GitlabUser: "gitlab-user", - GitlabToken: "gitlab-token", + GithubUser: "github-user", + GitlabUser: "gitlab-user", } terraformClient, err := terraform.NewClient(dataDir, "") Ok(t, err) diff --git a/server/server.go b/server/server.go index a0d3a040a1..d0e4ec57e1 100644 --- a/server/server.go +++ b/server/server.go @@ -217,10 +217,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { BitbucketServerURL: userConfig.BitbucketBaseURL, } commentParser := &events.CommentParser{ - GithubUser: userConfig.GithubUser, - GithubToken: userConfig.GithubToken, - GitlabUser: userConfig.GitlabUser, - GitlabToken: userConfig.GitlabToken, + GithubUser: userConfig.GithubUser, + GitlabUser: userConfig.GitlabUser, + BitbucketUser: userConfig.BitbucketUser, } defaultTfVersion := terraformClient.Version() pendingPlanFinder := &events.DefaultPendingPlanFinder{}