Run config validator before start

This commit is contained in:
Alex Vanin 2022-06-13 14:33:52 +03:00
parent 7692bfe779
commit 08a94ac0b2
4 changed files with 143 additions and 5 deletions

View file

@ -1,4 +1,3 @@
- [ ] Check config relations between runner and notification groups on start
- [ ] Add option to send "incident resolved" notifications - [ ] Add option to send "incident resolved" notifications
- [ ] Add Matrix notificator - [ ] Add Matrix notificator
- [ ] Add Telegram notificator - [ ] Add Telegram notificator

View file

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -54,15 +55,48 @@ func ReadConfig(file string) (*Config, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("config: %w", err) return nil, fmt.Errorf("config: %w", err)
} }
return parseConfig(data)
}
func parseConfig(data []byte) (*Config, error) {
c := new(Config) c := new(Config)
err = yaml.Unmarshal(data, c) if err := yaml.Unmarshal(data, c); err != nil {
if err != nil {
return nil, fmt.Errorf("config: %w", err) return nil, fmt.Errorf("config: %w", err)
} }
return c, validateConfig(c) return c, validateConfig(c)
} }
func validateConfig(_ *Config) error { func validateConfig(cfg *Config) error {
// todo(alexvanin): set defaults and validate values such as bad email addresses // 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 return nil
} }

102
config_test.go Normal file
View file

@ -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)
}
}
}

3
go.mod
View file

@ -5,12 +5,15 @@ go 1.18
require ( require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/robfig/cron/v3 v3.0.0 github.com/robfig/cron/v3 v3.0.0
github.com/stretchr/testify v1.7.0
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/zap v1.21.0 go.uber.org/zap v1.21.0
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99
) )
require ( 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/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect