From 08a94ac0b2f9a2068f38922e5eca04719b7b48e3 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Mon, 13 Jun 2022 14:33:52 +0300 Subject: [PATCH] Run config validator before start --- TODO.md | 1 - config.go | 42 ++++++++++++++++++-- config_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ 4 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 config_test.go diff --git a/TODO.md b/TODO.md index a727030..e87f8ea 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,3 @@ -- [ ] Check config relations between runner and notification groups on start - [ ] Add option to send "incident resolved" notifications - [ ] Add Matrix notificator - [ ] Add Telegram notificator diff --git a/config.go b/config.go index 041bb77..c7cb409 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" "time" "gopkg.in/yaml.v3" @@ -54,15 +55,48 @@ func ReadConfig(file string) (*Config, error) { if err != nil { return nil, fmt.Errorf("config: %w", err) } + return parseConfig(data) +} + +func parseConfig(data []byte) (*Config, error) { c := new(Config) - err = yaml.Unmarshal(data, c) - if err != nil { + if err := yaml.Unmarshal(data, c); err != nil { return nil, fmt.Errorf("config: %w", err) } return c, validateConfig(c) } -func validateConfig(_ *Config) error { - // todo(alexvanin): set defaults and validate values such as bad email addresses +func validateConfig(cfg *Config) error { + // Step 1: sanity check of notification groups + configuredNotifications := make(map[string]map[string]struct{}) + if cfg.Notifications.Email != nil { + emailMap := make(map[string]struct{}, len(cfg.Notifications.Email.Groups)) + for _, group := range cfg.Notifications.Email.Groups { + if len(group.Addresses) == 0 { + return fmt.Errorf("email group %s contains no addresses", group.Name) + } + if _, ok := emailMap[group.Name]; ok { + return fmt.Errorf("non unique email group name %s", group.Name) + } + emailMap[group.Name] = struct{}{} + } + configuredNotifications["email"] = emailMap + } + // With new notification channels, add more sanity checks + + // Step 2: sanity check of command notifications + for _, cmd := range cfg.Commands { + for _, n := range cmd.Notifications { + elements := strings.Split(n, ":") + if len(elements) != 2 { + return fmt.Errorf("invalid notification tuple %s in command %s", n, cmd.Name) + } else if groups, ok := configuredNotifications[elements[0]]; !ok { + return fmt.Errorf("invalid notification type %s in command %s", elements[0], cmd.Name) + } else if _, ok = groups[elements[1]]; !ok { + return fmt.Errorf("invalid notification group %s in command %s", elements[1], cmd.Name) + } + } + } + return nil } diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..7b5ffb0 --- /dev/null +++ b/config_test.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +var testCases = [...]struct { + config string + isValid bool + errMsg string +}{ + { + config: ` +state: + bolt: ./nz.state +notifications: + email: + smtp: smtp.gmail.com:587 + login: nzbx@corp.com + password: secret + groups: + - name: developers + addresses: + - alex@corp.com +commands: + - name: Test command + exec: ./script.sh arg1 + cron: "*/5 * * * *" + interval: 10s + timeout: 2s + threshold: 3 + threshold_sleep: 5s + notifications: + - email:developers`, + isValid: true, + }, + { + config: ` +notifications: + email: + groups: + - name: developers`, + errMsg: "contains no addresses", + }, + { + config: ` +notifications: + email: + groups: + - name: developers + addresses: + - foo@corp.com + - name: developers + addresses: + - bar@corp.com +`, + errMsg: "non unique email group name", + }, + { + config: ` +commands: + - name: Test command + notifications: + - hello wolrd`, + errMsg: "invalid notification tuple", + }, + { + config: ` +commands: + - name: Test command + notifications: + - foo:bar`, + errMsg: "invalid notification type", + }, + { + config: ` +notifications: + email: + groups: + - name: developers + addresses: + - foo@corp.com +commands: + - name: Test command + notifications: + - email:ops`, + errMsg: "invalid notification group", + }, +} + +func TestParseConfig(t *testing.T) { + for i, testCase := range testCases { + _, err := parseConfig([]byte(testCase.config)) + require.Equal(t, testCase.isValid, err == nil, fmt.Sprintf("test:%d, err:%s", i, err)) + if !testCase.isValid { + require.Contains(t, err.Error(), testCase.errMsg) + } + } +} diff --git a/go.mod b/go.mod index 96556bc..7bdcf99 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,15 @@ go 1.18 require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/robfig/cron/v3 v3.0.0 + github.com/stretchr/testify v1.7.0 go.etcd.io/bbolt v1.3.6 go.uber.org/zap v1.21.0 gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect