-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds the basic parser and its tests to parse Slack commands for locking and unlocking deployments per project/environment.
- Loading branch information
Showing
6 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package slackcmd | ||
|
||
type Lock struct { | ||
Project string | ||
Env string | ||
Reason string | ||
} | ||
|
||
func (l *Lock) Name() string { | ||
return "Lock" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package slackcmd | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
var lockUnlockPattern = regexp.MustCompile(`(unlock|lock) ([0-9a-zA-Z-]+) (staging|production|sandbox|stg|pro|prd)\s*(.*)`) | ||
|
||
func Parse(text string) (Command, error) { | ||
match := findLockUnlock(text) | ||
if match == nil { | ||
return nil, fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock <project> <env> [for <reason>]", text) | ||
} | ||
|
||
var ( | ||
command = match[0][1] | ||
project = match[0][2] | ||
env = match[0][3] | ||
reason = match[0][4] | ||
) | ||
|
||
switch command { | ||
case "unlock": | ||
if reason != "" { | ||
return nil, fmt.Errorf("invalid command %q: unlock command does not accept reason", text) | ||
} | ||
|
||
return &Unlock{ | ||
Project: project, | ||
Env: env, | ||
}, nil | ||
case "lock": | ||
if reason == "" { | ||
return nil, fmt.Errorf("invalid command %q: lock command requires reason", text) | ||
} | ||
|
||
if !strings.HasPrefix(reason, "for ") { | ||
return nil, fmt.Errorf("invalid command %q: reason must start with 'for'", text) | ||
} | ||
|
||
reason = strings.TrimPrefix(reason, "for ") | ||
|
||
return &Lock{ | ||
Project: project, | ||
Env: env, | ||
Reason: reason, | ||
}, nil | ||
default: | ||
panic("unreachable") | ||
} | ||
} | ||
|
||
func findLockUnlock(text string) [][]string { | ||
return lockUnlockPattern.FindAllStringSubmatch(text, -1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package slackcmd | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
validProjects = []string{"myproject1", "myproject-2"} | ||
invalidProjects = []string{"myproject#3", "myproject_4"} | ||
validEnvs = []string{"staging", "production", "sandbox", "stg", "pro", "prd"} | ||
invalidENvs = []string{"stg1", "pro1", "prd1", "prod", "test"} | ||
) | ||
|
||
func TestParse(t *testing.T) { | ||
type test struct { | ||
name string | ||
text string | ||
want Command | ||
err error | ||
} | ||
|
||
var tests = []test{} | ||
|
||
for i, p := range validProjects { | ||
for j, e := range validEnvs { | ||
tests = append(tests, test{ | ||
name: fmt.Sprintf("lock with valid project %d and env %d", i, j), | ||
text: fmt.Sprintf("lock %s %s for deployment of revision a", p, e), | ||
want: &Lock{Project: p, Env: e, Reason: "deployment of revision a"}, | ||
}) | ||
} | ||
} | ||
|
||
for i, p := range invalidProjects { | ||
for j, e := range invalidENvs { | ||
tests = append(tests, test{ | ||
name: fmt.Sprintf("lock with invalid project %d and env %d", i, j), | ||
text: fmt.Sprintf("lock %s %s for deployment of revision a", p, e), | ||
err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock <project> <env> [for <reason>]", fmt.Sprintf("lock %s %s for deployment of revision a", p, e)), | ||
}) | ||
} | ||
} | ||
|
||
for i, p := range validProjects { | ||
for j, e := range validEnvs { | ||
tests = append(tests, test{ | ||
name: fmt.Sprintf("unlock with valid project %d and env %d", i, j), | ||
text: fmt.Sprintf("unlock %s %s", p, e), | ||
want: &Unlock{Project: p, Env: e}, | ||
}) | ||
} | ||
} | ||
|
||
for i, p := range invalidProjects { | ||
for j, e := range invalidENvs { | ||
tests = append(tests, test{ | ||
name: fmt.Sprintf("unlock with invalid project %d and env %d", i, j), | ||
text: fmt.Sprintf("unlock %s %s", p, e), | ||
err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock <project> <env> [for <reason>]", fmt.Sprintf("unlock %s %s", p, e)), | ||
}) | ||
} | ||
} | ||
|
||
tests = append(tests, test{ | ||
name: "unlock has redundant reason", | ||
text: "unlock myproject1 production for deployment of revision a", | ||
err: fmt.Errorf("invalid command %q: unlock command does not accept reason", "unlock myproject1 production for deployment of revision a"), | ||
}) | ||
|
||
tests = append(tests, test{ | ||
name: "lock missing reason", | ||
text: "lock myproject1 production", | ||
err: fmt.Errorf("invalid command %q: lock command requires reason", "lock myproject1 production"), | ||
}) | ||
|
||
tests = append(tests, test{ | ||
name: "unknown command", | ||
text: "unknown myproject1 production for deployment of revision a", | ||
err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock <project> <env> [for <reason>]", "unknown myproject1 production for deployment of revision a"), | ||
}) | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := Parse(tt.text) | ||
assert.Equal(t, tt.want, got, "result") | ||
assert.Equal(t, tt.err, err, "error") | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package slackcmd | ||
|
||
type Command interface { | ||
Name() string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package slackcmd | ||
|
||
type Unlock struct { | ||
Project string | ||
Env string | ||
} | ||
|
||
func (u *Unlock) Name() string { | ||
return "Unlock" | ||
} |