Skip to content

Commit

Permalink
add cron validation
Browse files Browse the repository at this point in the history
  • Loading branch information
djelusic committed Dec 2, 2021
1 parent 6d15cc6 commit 399aafa
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 3 deletions.
95 changes: 95 additions & 0 deletions domain/cron.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package domain

import (
"regexp"
"strconv"
"strings"
)

// Stolen from: https://gist.github.com/ultrasonex/e1fdb8354408a56df91aa4902d17aa6a
var (
minuteRegex = regexp.MustCompile(`^([*]|([0]?[0-5]?[0-9]?)|(([*]|([0]?[0-5]?[0-9]?))(\/|\-)([0]?[0-5]?[0-9]?))|(([0]?[0-5]?[0-9]?)((\,)([0]?[0-5]?[0-9]?))*))$`)
hourRegex = regexp.MustCompile(`^([*]|[01]?[0-9]|2[0-3]|(([*]|([01]?[0-9]|2[0-3]?))(\/|\-)([01]?[0-9]|2[0-3]?))|(([01]?[0-9]|2[0-3]?)((\,)([01]?[0-9]|2[0-3]?))*))$`)
dayOfMonthRegex = regexp.MustCompile(`^([*]|[?]|([0-2]?[0-9]|3[0-1])|(([*]|([0-2]?[0-9]|3[0-1]))(\/|\-)([0-2]?[0-9]|3[0-1]))|(([0-2]?[0-9]|3[0-1])((\,)([0-2]?[0-9]|3[0-1]))*))$`)
monthRegex = regexp.MustCompile(
`^([*]|([0]?[0-9]|1[0-2])|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|` +
`((([*]|([0]?[0-9]|1[0-2])|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)))(\/|\-)(([0]?[0-9]|1[0-2])|` +
`(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)))|((([0]?[0-9]|1[0-2])|` +
`(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))((\,)(([0]?[0-9]|1[0-2])|` +
`(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)))*))$`,
)
dayOfWeekRegex = regexp.MustCompile(
`([*]|[?]|([0]?[1-7])|` +
`(SUN|MON|TUE|WED|THU|FRI|SAT)|((([0]?[1-7])|` +
`([*]|(SUN|MON|TUE|WED|THU|FRI|SAT)))(\/|\-|\,|\#)(([0]?[1-7])|` +
`(SUN|MON|TUE|WED|THU|FRI|SAT)))|((([0]?[1-7])|` +
`(SUN|MON|TUE|WED|THU|FRI|SAT))((\,)(([0]?[1-7])|` +
`(SUN|MON|TUE|WED|THU|FRI|SAT)))*))$`,
)
yearRegex = regexp.MustCompile(`^([*]|([1-2][01][0-9][0-9])|(([1-2][01][0-9][0-9])(\/|\-)([1-2][01][0-9][0-9]))|(([1-2][01][0-9][0-9])((\,)([1-2][01][0-9][0-9]))*))$`)
)

func ValidateAWSCron(cron string) bool {
parts := strings.Split(cron, " ")
if len(parts) != 6 {
return false
}
res := []*regexp.Regexp{
minuteRegex,
hourRegex,
dayOfMonthRegex,
monthRegex,
dayOfWeekRegex,
yearRegex,
}
for idx, re := range res {
if !re.MatchString(parts[idx]) {
return false
}
}
if !validateDays(parts[2], parts[4]) {
return false
}
if !validateYear(parts[5]) {
return false
}
return true
}

func validateDays(dayOfMonth, dayOfWeek string) bool {
if dayOfMonth != "?" && dayOfWeek != "?" ||
dayOfMonth == "?" && dayOfWeek == "?" {
return false
}
if strings.Contains(dayOfWeek, "#") {
parts := strings.Split(dayOfWeek, "#")
if len(parts) < 2 {
return false
}
d, err := strconv.Atoi(parts[1])
if err != nil || d > 5 {
return false
}
}
return true
}

func validateYear(year string) bool {
var sep string
switch {
case strings.Contains(year, ","):
sep = ","
case strings.Contains(year, "-"):
sep = "-"
}
if sep != "" {
parts := strings.Split(year, sep)
for _, p := range parts {
y, err := strconv.Atoi(p)
if err != nil || (y < 1970 || y > 2199) {
return false
}
}
}
return true
}
54 changes: 54 additions & 0 deletions domain/cron_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package domain

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestValidateAWSCron(t *testing.T) {
cases := []struct {
cron string
valid bool
}{
{"* * * * ? *", true},
{"*/1 * * * ? *", true},
{"* */1 * * ? *", true},
{"* * */1 * ? *", true},
{"* * * */1 ? *", true},
{"0 18 ? * MON-FRI *", true},
{"0 10 * * ? *", true},
{"15 12 * * ? *", true},
{"0 8 1 * ? *", true},
{"0/5 8-17 ? * MON-FRI *", true},
{"0 9 ? * 2#1 *", true},
{"0 07/12 ? * * *", true},
{"10,20,30,40 07/12 ? * * *", true},
{"10 10,15,20,23 ? * * *", true},
{"10 10 15,30,31 * ? *", true},
{"10 10 15 JAN,JUL,DEC ? *", true},
{"10 10 31 04,09,12 ? *", true},
{"0,5 07/12 ? * 01,05,7 *", true},
{"0,5 07/12 ? * 01,05,7 2020,2021,2028,2199", true},
{"0,5 07/12 ? * 01,05,7 2020,2021,2028,2199", true},
{"0,5 07/12 ? * 01,05,7 2020-2199", true},

{"* * * * * *", false},
{"0 18 ? * MON-FRI", false},
{"0 18 * * * *", false},
{"0 65 * * ? *", false},
{"89 10 * * ? *", false},
{"15/65 10 * * ? *", false},
{"15/30 10 * * ? 2400", false},
{"0 9 ? * 2#6 *", false},
{"0 9 ? * ? *", false},
{"10 10 31 04,09,13 ? *", false},
{"0,5 07/12 ? * 01,05,8 *", false},
{"0,5 07/12 ? * 01,05,7 2020,2021,2028,1111", false},
{"0,5 07/12 ? * 01,05,7 2020,2021,2028,1969", false},
{"0,5 07/12 ? * 01,05,7 1969-2100", false},
}
for _, c := range cases {
require.Equal(t, c.valid, ValidateAWSCron(c.cron), c.cron)
}
}
4 changes: 2 additions & 2 deletions domain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ var (
ErrWorkspaceNotFound = fmt.Errorf("workspace not found")
)

type EvironmentConfigValidationError struct {
type EnvironmentConfigValidationError struct {
Err error
}

func (e *EvironmentConfigValidationError) Error() string {
func (e *EnvironmentConfigValidationError) Error() string {
return e.Err.Error()
}
7 changes: 7 additions & 0 deletions domain/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ func (fc *FunctionConfiguration) merge(sources ...FunctionConfiguration) bool {
func (fc *FunctionConfiguration) changed(original *FunctionConfiguration) bool {
return !reflect.DeepEqual(fc, original)
}

func (fc *FunctionConfiguration) validateCron() bool {
if fc.Cron != "" && !ValidateAWSCron(fc.Cron) {
return false
}
return true
}
25 changes: 24 additions & 1 deletion domain/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,33 @@ func ValidateEnvironmentConfig(buf []byte) (*EnvironmentConfig, error) {
return nil, errors.WithStack(err)
}
if err := schema.ValidateYAML(buf); err != nil {
return nil, &EvironmentConfigValidationError{err}
return nil, &EnvironmentConfigValidationError{err}
}
if err := yaml.Unmarshal(buf, ec); err != nil {
return nil, errors.WithStack(err)
}
if !ec.validateCron() {
return nil, &EnvironmentConfigValidationError{
fmt.Errorf("invalid cron syntax"),
}
}
return ec, nil
}

func (ec *EnvironmentConfig) validateCron() bool {
p := ec.Project
if !p.validateCron() {
return false
}
for _, s := range p.Stages {
if !s.validateCron() {
return false
}
for _, f := range s.Functions {
if !f.validateCron() {
return false
}
}
}
return true
}

0 comments on commit 399aafa

Please sign in to comment.