Skip to content

Commit aa99adb

Browse files
lkysowLuke Kysow
authored and
Luke Kysow
committed
New parsing of comments. Accept all flags
1 parent 2822b3a commit aa99adb

7 files changed

+115
-67
lines changed

server/apply_executor.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
145145
}
146146
}
147147

148-
tfApplyCmd := append([]string{"apply", "-no-color", plan.LocalPath}, applyExtraArgs...)
148+
tfApplyCmd := append(append([]string{"apply", "-no-color", plan.LocalPath}, applyExtraArgs...), ctx.Command.Flags...)
149149
output, err := a.terraform.RunCommandWithVersion(ctx.Log, absolutePath, tfApplyCmd, terraformVersion)
150150
if err != nil {
151151
return ProjectResult{Error: fmt.Errorf("%s\n%s", err.Error(), output)}

server/command_handler.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ func (c CommandName) String() string {
6464
}
6565

6666
type Command struct {
67-
Verbose bool
68-
Environment string
6967
Name CommandName
68+
Environment string
69+
Verbose bool
70+
Flags []string
7071
}
7172

7273
func (c *CommandHandler) ExecuteCommand(ctx *CommandContext) {

server/event_parser.go

+59-41
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package server
33
import (
44
"errors"
55
"fmt"
6-
"regexp"
76
"strings"
87

98
"github.com/google/go-github/github"
@@ -15,71 +14,71 @@ type EventParser struct {
1514
GithubToken string
1615
}
1716

17+
// DetermineCommand parses the comment as an atlantis command. If it succeeds,
18+
// it returns the command. Otherwise it returns error.
1819
func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Command, error) {
19-
// regex matches:
20+
// valid commands contain:
2021
// the initial "executable" name, 'run' or 'atlantis' or '@GithubUser' where GithubUser is the api user atlantis is running as
2122
// then a command, either 'plan', 'apply', or 'help'
22-
// then an optional environment and an optional --verbose flag
23+
// then an optional environment argument, an optional --verbose flag and any other flags
2324
//
2425
// examples:
2526
// atlantis help
2627
// run plan
2728
// @GithubUser plan staging
2829
// atlantis plan staging --verbose
29-
//
30-
// capture groups:
31-
// 1: the command, i.e. plan/apply/help
32-
// 2: the environment OR the --verbose flag (if they didn't specify and environment)
33-
// 3: the --verbose flag (if they specified an environment)
34-
atlantisCommentRegex := `^(?:run|atlantis|@` + e.GithubUser + `)[[:blank:]]+(plan|apply|help)(?:[[:blank:]]+([a-zA-Z0-9_-]+))?[[:blank:]]*(--verbose)?$`
35-
runPlanMatcher := regexp.MustCompile(atlantisCommentRegex)
36-
30+
// atlantis plan staging --verbose -key=value -key2 value2
3731
commentBody := comment.Comment.GetBody()
3832
if commentBody == "" {
3933
return nil, errors.New("comment.body is null")
4034
}
41-
42-
// extract the command and environment. ex. for "atlantis plan staging", the command is "plan", and the environment is "staging"
43-
match := runPlanMatcher.FindStringSubmatch(commentBody)
44-
if len(match) < 4 {
45-
var truncated = commentBody
46-
if len(truncated) > 30 {
47-
truncated = truncated[0:30] + "..."
48-
}
49-
return nil, errors.New("not an Atlantis command")
50-
}
51-
52-
// depending on the comment, the command/env/verbose may be in different matching groups
53-
// if there is no env (ex. just atlantis plan --verbose) then, verbose would be in the 2nd group
54-
// if there is an env, then verbose would be in the 3rd
55-
command := match[1]
56-
env := match[2]
57-
verboseFlag := match[3]
58-
if verboseFlag == "" && env == "--verbose" {
59-
verboseFlag = env
60-
env = ""
35+
err := errors.New("not an Atlantis command")
36+
args := strings.Fields(commentBody)
37+
if len(args) < 2 {
38+
return nil, err
6139
}
6240

63-
// now we're ready to actually look at the values
41+
env := "default"
6442
verbose := false
65-
if verboseFlag == "--verbose" {
66-
verbose = true
43+
var flags []string
44+
45+
if !e.stringInSlice(args[0], []string{"run", "atlantis", "@" + e.GithubUser}) {
46+
return nil, err
47+
}
48+
if !e.stringInSlice(args[1], []string{"plan", "apply", "help"}) {
49+
return nil, err
50+
}
51+
if args[1] == "help" {
52+
return &Command{Name: Help}, nil
6753
}
68-
// if env not specified, use terraform's default
69-
if env == "" {
70-
env = "default"
54+
command := args[1]
55+
56+
if len(args) > 2 {
57+
flags = args[2:]
58+
59+
// if the third arg doesn't start with '-' then we assume it's an
60+
// environment not a flag
61+
if !strings.HasPrefix(args[2], "-") {
62+
env = args[2]
63+
flags = args[3:]
64+
}
65+
66+
// check for --verbose specially and then remove any additional
67+
// occurrences
68+
if e.stringInSlice("--verbose", flags) {
69+
verbose = true
70+
flags = e.removeOccurrences("--verbose", flags)
71+
}
7172
}
7273

73-
c := &Command{Verbose: verbose, Environment: env}
74+
c := &Command{Verbose: verbose, Environment: env, Flags: flags}
7475
switch command {
7576
case "plan":
7677
c.Name = Plan
7778
case "apply":
7879
c.Name = Apply
79-
case "help":
80-
c.Name = Help
8180
default:
82-
return nil, fmt.Errorf("something went wrong with our regex, the command we parsed %q was not apply or plan", match[1])
81+
return nil, fmt.Errorf("something went wrong parsing the command, the command we parsed %q was not apply or plan", command)
8382
}
8483
return c, nil
8584
}
@@ -189,3 +188,22 @@ func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, e
189188
Name: repoName,
190189
}, nil
191190
}
191+
192+
func (e *EventParser) stringInSlice(a string, list []string) bool {
193+
for _, b := range list {
194+
if b == a {
195+
return true
196+
}
197+
}
198+
return false
199+
}
200+
201+
func (e *EventParser) removeOccurrences(a string, list []string) []string {
202+
var out []string
203+
for _, b := range list {
204+
if b != a {
205+
out = append(out, b)
206+
}
207+
}
208+
return out
209+
}

server/event_parser_test.go

+48-19
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package server_test
22

33
import (
4-
"fmt"
54
"testing"
65

76
"github.com/google/go-github/github"
87
"github.com/hootsuite/atlantis/server"
98
. "github.com/hootsuite/atlantis/testing_util"
9+
"strings"
1010
)
1111

1212
func TestDetermineCommandInvalid(t *testing.T) {
@@ -22,8 +22,6 @@ func TestDetermineCommandInvalid(t *testing.T) {
2222
"atlantis slkjd",
2323
"@user slkjd",
2424
"atlantis plans",
25-
// whitespace
26-
" atlantis plan",
2725
// misc
2826
"related comment mentioning atlantis",
2927
}
@@ -55,29 +53,50 @@ func TestDetermineCommandPermutations(t *testing.T) {
5553
execNames := []string{"run", "atlantis", "@user"}
5654
commandNames := []server.CommandName{server.Plan, server.Apply}
5755
envs := []string{"", "default", "env", "env-dash", "env_underscore", "camelEnv"}
58-
verboses := []bool{true, false}
56+
flagCases := [][]string{
57+
[]string{},
58+
[]string{"--verbose"},
59+
[]string{"-key=value"},
60+
[]string{"-key", "value"},
61+
[]string{"-key1=value1", "-key2=value2"},
62+
[]string{"-key1=value1", "-key2", "value2"},
63+
[]string{"-key1", "value1", "-key2=value2"},
64+
[]string{"--verbose", "key2=value2"},
65+
[]string{"-key1=value1", "--verbose"},
66+
}
5967

6068
// test all permutations
6169
for _, exec := range execNames {
6270
for _, name := range commandNames {
6371
for _, env := range envs {
64-
for _, v := range verboses {
65-
vFlag := ""
66-
if v == true {
67-
vFlag = "--verbose"
68-
}
72+
for _, flags := range flagCases {
73+
// If github comments end in a newline they get \r\n appended.
74+
// Ensure that we parse commands properly either way.
75+
for _, lineEnding := range []string{"", "\r\n"} {
76+
comment := strings.Join(append([]string{exec, name.String(), env}, flags...), " ") + lineEnding
77+
t.Log("testing comment: " + comment)
78+
c, err := e.DetermineCommand(buildComment(comment))
79+
Ok(t, err)
80+
Equals(t, name, c.Name)
81+
if env == "" {
82+
Equals(t, "default", c.Environment)
83+
} else {
84+
Equals(t, env, c.Environment)
85+
}
86+
Equals(t, stringInSlice("--verbose", flags), c.Verbose)
6987

70-
comment := fmt.Sprintf("%s %s %s %s", exec, name.String(), env, vFlag)
71-
t.Log("testing comment: " + comment)
72-
c, err := e.DetermineCommand(buildComment(comment))
73-
Ok(t, err)
74-
Equals(t, name, c.Name)
75-
if env == "" {
76-
Equals(t, "default", c.Environment)
77-
} else {
78-
Equals(t, env, c.Environment)
88+
// ensure --verbose never shows up in flags
89+
for _, f := range c.Flags {
90+
Assert(t, f != "--verbose", "Should not pass on the --verbose flag: %v", flags)
91+
}
92+
93+
// check all flags are present
94+
for _, f := range flags {
95+
if f != "--verbose" {
96+
Contains(t, f, c.Flags)
97+
}
98+
}
7999
}
80-
Equals(t, v, c.Verbose)
81100
}
82101
}
83102
}
@@ -91,3 +110,13 @@ func buildComment(c string) *github.IssueCommentEvent {
91110
},
92111
}
93112
}
113+
114+
func stringInSlice(a string, list []string) bool {
115+
for _, b := range list {
116+
if b == a {
117+
return true
118+
}
119+
}
120+
return false
121+
}
122+

server/plan_executor.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ func (p *PlanExecutor) plan(ctx *CommandContext, repoDir string, project models.
148148

149149
// Run terraform plan
150150
planFile := filepath.Join(repoDir, project.Path, fmt.Sprintf("%s.tfplan", tfEnv))
151-
tfPlanCmd := append([]string{"plan", "-refresh", "-no-color", "-out", planFile, "-var", fmt.Sprintf("%s=%s", atlantisUserTFVar, ctx.User.Username)}, planExtraArgs...)
151+
userVar := fmt.Sprintf("%s=%s", atlantisUserTFVar, ctx.User.Username)
152+
tfPlanCmd := append(append([]string{"plan", "-refresh", "-no-color", "-out", planFile, "-var", userVar}, planExtraArgs...), ctx.Command.Flags...)
152153

153154
// check if env/{environment}.tfvars exist
154155
tfEnvFileName := filepath.Join("env", tfEnv+".tfvars")

server/server.go

-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,6 @@ func (s *Server) handleCommentEvent(w http.ResponseWriter, event *gh.IssueCommen
384384
return
385385
}
386386

387-
// determine if the comment matches a plan or apply command
388387
ctx := &CommandContext{}
389388
command, err := s.eventParser.DetermineCommand(event)
390389
if err != nil {

testing_util/testing_util.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ func Equals(tb testing.TB, exp, act interface{}) {
3838
}
3939

4040
// Contains fails the test if the slice doesn't contain the expected element
41-
func Contains(tb testing.TB, exp interface{}, slice []interface{}) {
41+
func Contains(tb testing.TB, exp interface{}, slice []string) {
4242
for _, v := range slice {
43-
if reflect.DeepEqual(v, exp) {
43+
if v == exp {
4444
return
4545
}
4646
}

0 commit comments

Comments
 (0)