diff --git a/alert.go b/alert.go index 4935216..b034610 100644 --- a/alert.go +++ b/alert.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os/exec" - "strings" "text/template" "time" @@ -21,8 +20,9 @@ var ( // Alert is a config driven mechanism for sending a notice type Alert struct { - Name string - Command CommandOrShell + Name string `hcl:"name,label"` + Command []string + ShellCommand string `hcl:"shell_command"` commandTemplate []*template.Template commandShellTemplate *template.Template } @@ -40,41 +40,24 @@ type AlertNotice struct { // IsValid returns a boolean indicating if the Alert has been correctly // configured func (alert Alert) IsValid() bool { - return !alert.Command.Empty() + return ((alert.Command != nil || alert.ShellCommand != "") && + !(alert.Command != nil && alert.ShellCommand != "")) } // BuildTemplates compiles command templates for the Alert func (alert *Alert) BuildTemplates() error { - // TODO: Remove legacy template support later after 1.0 - legacy := strings.NewReplacer( - "{alert_count}", "{{.AlertCount}}", - "{alert_message}", "{{.MonitorName}} check has failed {{.FailureCount}} times", - "{failure_count}", "{{.FailureCount}}", - "{last_output}", "{{.LastCheckOutput}}", - "{last_success}", "{{.LastSuccess}}", - "{monitor_name}", "{{.MonitorName}}", - ) - slog.Debugf("Building template for alert %s", alert.Name) switch { - case alert.commandTemplate == nil && alert.Command.Command != nil: + case alert.commandTemplate == nil && alert.Command != nil: alert.commandTemplate = []*template.Template{} - for i, cmdPart := range alert.Command.Command { - if PyCompat { - cmdPart = legacy.Replace(cmdPart) - } - + for i, cmdPart := range alert.Command { alert.commandTemplate = append(alert.commandTemplate, template.Must( template.New(alert.Name+fmt.Sprint(i)).Parse(cmdPart), )) } - case alert.commandShellTemplate == nil && alert.Command.ShellCommand != "": - shellCmd := alert.Command.ShellCommand - - if PyCompat { - shellCmd = legacy.Replace(shellCmd) - } + case alert.commandShellTemplate == nil && alert.ShellCommand != "": + shellCmd := alert.ShellCommand alert.commandShellTemplate = template.Must( template.New(alert.Name).Parse(shellCmd), @@ -151,11 +134,9 @@ func (alert Alert) Send(notice AlertNotice) (outputStr string, err error) { func NewLogAlert() *Alert { return &Alert{ Name: "log", - Command: CommandOrShell{ - Command: []string{ - "echo", - "{{.MonitorName}} {{if .IsUp}}has recovered{{else}}check has failed {{.FailureCount}} times{{end}}", - }, + Command: []string{ + "echo", + "{{.MonitorName}} {{if .IsUp}}has recovered{{else}}check has failed {{.FailureCount}} times{{end}}", }, } } diff --git a/alert_test.go b/alert_test.go index 18c4bdc..fbb747c 100644 --- a/alert_test.go +++ b/alert_test.go @@ -11,8 +11,8 @@ func TestAlertIsValid(t *testing.T) { expected bool name string }{ - {Alert{Command: CommandOrShell{Command: []string{"echo", "test"}}}, true, "Command only"}, - {Alert{Command: CommandOrShell{ShellCommand: "echo test"}}, true, "CommandShell only"}, + {Alert{Command: []string{"echo", "test"}}, true, "Command only"}, + {Alert{ShellCommand: "echo test"}, true, "CommandShell only"}, {Alert{}, false, "No commands"}, } @@ -36,47 +36,34 @@ func TestAlertSend(t *testing.T) { expectedOutput string expectErr bool name string - pyCompat bool }{ { - Alert{Command: CommandOrShell{Command: []string{"echo", "{{.MonitorName}}"}}}, + Alert{Command: []string{"echo", "{{.MonitorName}}"}}, AlertNotice{MonitorName: "test"}, "test\n", false, "Command with template", - false, }, { - Alert{Command: CommandOrShell{ShellCommand: "echo {{.MonitorName}}"}}, + Alert{ShellCommand: "echo {{.MonitorName}}"}, AlertNotice{MonitorName: "test"}, "test\n", false, "Command shell with template", - false, }, { - Alert{Command: CommandOrShell{Command: []string{"echo", "{{.Bad}}"}}}, + Alert{Command: []string{"echo", "{{.Bad}}"}}, AlertNotice{MonitorName: "test"}, "", true, "Command with bad template", - false, }, { - Alert{Command: CommandOrShell{ShellCommand: "echo {{.Bad}}"}}, + Alert{ShellCommand: "echo {{.Bad}}"}, AlertNotice{MonitorName: "test"}, "", true, "Command shell with bad template", - false, - }, - { - Alert{Command: CommandOrShell{ShellCommand: "echo {alert_message}"}}, - AlertNotice{MonitorName: "test", FailureCount: 1}, - "test check has failed 1 times\n", - false, - "Command shell with legacy template", - true, }, // Test default log alert down { @@ -85,7 +72,6 @@ func TestAlertSend(t *testing.T) { "Test check has failed 1 times\n", false, "Default log alert down", - false, }, // Test default log alert up { @@ -94,15 +80,11 @@ func TestAlertSend(t *testing.T) { "Test has recovered\n", false, "Default log alert up", - false, }, } for _, c := range cases { log.Printf("Testing case %s", c.name) - // Set PyCompat to value of compat flag - PyCompat = c.pyCompat - err := c.alert.BuildTemplates() if err != nil { t.Errorf("Send(%v output), error building templates: %v", c.name, err) @@ -121,9 +103,6 @@ func TestAlertSend(t *testing.T) { log.Printf("Case failed: %s", c.name) } - // Set PyCompat back to default value - PyCompat = false - log.Println("-----") } } @@ -146,8 +125,8 @@ func TestAlertBuildTemplate(t *testing.T) { expectErr bool name string }{ - {Alert{Command: CommandOrShell{Command: []string{"echo", "test"}}}, false, "Command only"}, - {Alert{Command: CommandOrShell{ShellCommand: "echo test"}}, false, "CommandShell only"}, + {Alert{Command: []string{"echo", "test"}}, false, "Command only"}, + {Alert{ShellCommand: "echo test"}, false, "CommandShell only"}, {Alert{}, true, "No commands"}, } diff --git a/config.go b/config.go index a389b2b..fbc6b04 100644 --- a/config.go +++ b/config.go @@ -2,82 +2,93 @@ package main import ( "errors" - "io/ioutil" "time" "git.iamthefij.com/iamthefij/slog" - "gopkg.in/yaml.v2" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/hcl/v2/hclsimple" ) var errInvalidConfig = errors.New("Invalid configuration") // Config type is contains all provided user configuration type Config struct { - CheckInterval SecondsOrDuration `yaml:"check_interval"` - Monitors []*Monitor - Alerts map[string]*Alert + CheckInterval time.Duration `hcl:"check_interval"` + Monitors []*Monitor `hcl:"monitor,block"` + Alerts AlertContainer `hcl:"alerts,block"` } -// CommandOrShell type wraps a string or list of strings -// for executing a command directly or in a shell -type CommandOrShell struct { - ShellCommand string - Command []string -} +func (p *Parser) decodeConfig(block *hcl.Block, ectx *hcl.EvalContext) (*Config, hcl.Diagnostics) { + var b struct { + CheckInterval string `hcl:"check_interval"` + Monitors []*Monitor `hcl:"monitor,block"` + Alerts AlertContainer `hcl:"alerts,block"` + } + diags := gohcl.DecodeBody(block.Body, ectx, &b) + if diags.HasErrors() { + return nil, diags + } -// Empty checks if the Command has a value -func (cos CommandOrShell) Empty() bool { - return (cos.ShellCommand == "" && cos.Command == nil) -} + config := &Config{ + Monitors: b.Monitors, + Alerts: b.Alerts, + } -// UnmarshalYAML allows unmarshalling either a string or slice of strings -// and parsing them as either a command or a shell command. -func (cos *CommandOrShell) UnmarshalYAML(unmarshal func(interface{}) error) error { - var cmd []string - err := unmarshal(&cmd) - // Error indicates this is shell command + // Set a default for CheckInterval + if b.CheckInterval == "" { + b.CheckInterval = "30s" + } + + checkInterval, err := time.ParseDuration(b.CheckInterval) if err != nil { - var shellCmd string + return nil, append(diags, &hcl.Diagnostic{ + Summary: "Failed to parse check_interval duration", + Severity: hcl.DiagError, + Detail: err.Error(), + Subject: &block.DefRange, + }) + } + config.CheckInterval = checkInterval - err := unmarshal(&shellCmd) - if err != nil { - return err + return config, diags +} + +// AlertContainer is struct wrapping map access to alerts +type AlertContainer struct { + Alerts []*Alert `hcl:"alert,block"` + alertLookup map[string]*Alert +} + +// Get returns an alert based on it's name +func (ac AlertContainer) Get(name string) (*Alert, bool) { + // Build lookup map on first run + if ac.alertLookup == nil { + ac.alertLookup = map[string]*Alert{} + for _, alert := range ac.Alerts { + ac.alertLookup[alert.Name] = alert } - - cos.ShellCommand = shellCmd - } else { - cos.Command = cmd } - return nil + v, ok := ac.alertLookup[name] + + return v, ok } -// SecondsOrDuration wraps a duration value for parsing a duration or seconds from YAML -// NOTE: This should be removed in favor of only parsing durations once compatibility is broken -type SecondsOrDuration struct { - value time.Duration +// IsEmpty checks if there are any defined alerts +func (ac AlertContainer) IsEmpty() bool { + return ac.Alerts == nil || len(ac.Alerts) == 0 } -// Value returns a duration value -func (sod SecondsOrDuration) Value() time.Duration { - return sod.value -} - -// UnmarshalYAML allows unmarshalling a duration value or seconds if an int was provided -func (sod *SecondsOrDuration) UnmarshalYAML(unmarshal func(interface{}) error) error { - var seconds int64 - err := unmarshal(&seconds) - - if err == nil { - sod.value = time.Second * time.Duration(seconds) - - return nil +// BuildAllTemplates builds all alert templates +func (ac *AlertContainer) BuildAllTemplates() (err error) { + for _, alert := range ac.Alerts { + if err = alert.BuildTemplates(); err != nil { + return + } } - // Error indicates that we don't have an int - err = unmarshal(&sod.value) - - return err + return } // IsValid checks config validity and returns true if valid @@ -85,14 +96,14 @@ func (config Config) IsValid() (isValid bool) { isValid = true // Validate alerts - if config.Alerts == nil || len(config.Alerts) == 0 { + if config.Alerts.IsEmpty() { // This should never happen because there is a default alert named 'log' for now slog.Errorf("Invalid alert configuration: Must provide at least one alert") isValid = false } - for _, alert := range config.Alerts { + for _, alert := range config.Alerts.Alerts { if !alert.IsValid() { slog.Errorf("Invalid alert configuration: %+v", alert.Name) @@ -118,7 +129,7 @@ func (config Config) IsValid() (isValid bool) { // Check that all Monitor alerts actually exist for _, isUp := range []bool{true, false} { for _, alertName := range monitor.GetAlertNames(isUp) { - if _, ok := config.Alerts[alertName]; !ok { + if _, ok := config.Alerts.Get(alertName); !ok { slog.Errorf( "Invalid monitor configuration: %s. Unknown alert %s", monitor.Name, alertName, @@ -135,42 +146,20 @@ func (config Config) IsValid() (isValid bool) { // Init performs extra initialization on top of loading the config from file func (config *Config) Init() (err error) { - for name, alert := range config.Alerts { - alert.Name = name - if err = alert.BuildTemplates(); err != nil { - return - } - } + err = config.Alerts.BuildAllTemplates() return } // LoadConfig will read config from the given path and parse it func LoadConfig(filePath string) (config Config, err error) { - data, err := ioutil.ReadFile(filePath) - if err != nil { - return - } - - err = yaml.Unmarshal(data, &config) + err = hclsimple.DecodeFile(filePath, nil, &config) if err != nil { return } slog.Debugf("Config values:\n%v\n", config) - // Add log alert if not present - if PyCompat { - // Initialize alerts list if not present - if config.Alerts == nil { - config.Alerts = map[string]*Alert{} - } - - if _, ok := config.Alerts["log"]; !ok { - config.Alerts["log"] = NewLogAlert() - } - } - // Finish initializing configuration if err = config.Init(); err != nil { return diff --git a/config_test.go b/config_test.go index 6945b40..2d7ebb1 100644 --- a/config_test.go +++ b/config_test.go @@ -11,21 +11,42 @@ func TestLoadConfig(t *testing.T) { configPath string expectErr bool name string - pyCompat bool }{ - {"./test/valid-config.yml", false, "Valid config file", false}, - {"./test/valid-default-log-alert.yml", false, "Valid config file with default log alert PyCompat", true}, - {"./test/valid-default-log-alert.yml", true, "Invalid config file no log alert", false}, - {"./test/does-not-exist", true, "Invalid config path", false}, - {"./test/invalid-config-type.yml", true, "Invalid config type for key", false}, - {"./test/invalid-config-missing-alerts.yml", true, "Invalid config missing alerts", false}, - {"./test/invalid-config-unknown-alert.yml", true, "Invalid config unknown alert", false}, + { + "./test/valid-config.hcl", + false, + "Valid config file", + }, + { + "./test/valid-default-log-alert.yml", + true, + "Invalid config file no log alert", + }, + { + "./test/does-not-exist", + true, + "Invalid config path", + }, + { + "./test/invalid-config-type.yml", + true, + "Invalid config type for key", + }, + { + "./test/invalid-config-missing-alerts.yml", + true, + "Invalid config missing alerts", + }, + { + "./test/invalid-config-unknown-alert.yml", + true, + "Invalid config unknown alert", + }, } for _, c := range cases { log.Printf("Testing case %s", c.name) // Set PyCompat based on compatibility mode - PyCompat = c.pyCompat _, err := LoadConfig(c.configPath) hasErr := (err != nil) @@ -33,9 +54,6 @@ func TestLoadConfig(t *testing.T) { t.Errorf("LoadConfig(%v), expected_error=%v actual=%v", c.name, c.expectErr, err) log.Printf("Case failed: %s", c.name) } - - // Set PyCompat to default value - PyCompat = false } } @@ -52,15 +70,15 @@ func TestIntervalParsing(t *testing.T) { oneMinute := time.Minute // validate top level interval seconds represented as an int - if config.CheckInterval.Value() != oneSecond { + if config.CheckInterval != oneSecond { t.Errorf("Incorrectly parsed int seconds. expected=%v actual=%v", oneSecond, config.CheckInterval) } - if config.Monitors[0].CheckInterval.Value() != tenSeconds { + if config.Monitors[0].CheckInterval != tenSeconds { t.Errorf("Incorrectly parsed seconds duration. expected=%v actual=%v", oneSecond, config.CheckInterval) } - if config.Monitors[1].CheckInterval.Value() != oneMinute { + if config.Monitors[1].CheckInterval != oneMinute { t.Errorf("Incorrectly parsed seconds duration. expected=%v actual=%v", oneSecond, config.CheckInterval) } @@ -81,7 +99,7 @@ func TestMultiLineConfig(t *testing.T) { log.Println("TestMultiLineConfig(parse > string)") expected := "echo 'Some string with stuff'; echo \"\"; exit 1\n" - actual := config.Monitors[0].Command.ShellCommand + actual := config.Monitors[0].ShellCommand if expected != actual { t.Errorf("TestMultiLineConfig(>) failed") @@ -96,7 +114,7 @@ func TestMultiLineConfig(t *testing.T) { _, notice := config.Monitors[0].Check() if notice == nil { - t.Fatalf("Did not receive an alert notice") + t.Fatal("Did not receive an alert notice") } expected = "Some string with stuff\n\n" @@ -114,8 +132,13 @@ func TestMultiLineConfig(t *testing.T) { log.Println("TestMultiLineConfig(parse | string)") expected = "echo 'Some string with stuff'\necho ''\n" - actual = config.Alerts["log_shell"].Command.ShellCommand + alert, ok := config.Alerts.Get("log_shell") + if !ok { + t.Fatal("Could not find expected alert 'log_shell'") + } + + actual = alert.ShellCommand if expected != actual { t.Errorf("TestMultiLineConfig(|) failed") t.Logf("string expected=`%v`", expected) @@ -127,7 +150,7 @@ func TestMultiLineConfig(t *testing.T) { log.Println("-----") log.Println("TestMultiLineConfig(execute | string)") - actual, err = config.Alerts["log_shell"].Send(AlertNotice{}) + actual, err = alert.Send(AlertNotice{}) if err != nil { t.Errorf("Execution of alert failed") } diff --git a/go.mod b/go.mod index 7542551..723bf8c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.15 require ( git.iamthefij.com/iamthefij/slog v1.3.0 + github.com/hashicorp/hcl/v2 v2.11.1 github.com/prometheus/client_golang v1.2.1 - gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 033ad7f..e466d67 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,16 @@ git.iamthefij.com/iamthefij/slog v1.3.0 h1:4Hu5PQvDrW5e3FrTS3q2iIXW0iPvhNY/9qJsqDR3K3I= git.iamthefij.com/iamthefij/slog v1.3.0/go.mod h1:1RUj4hcCompZkAxXCRfUX786tb3cM/Zpkn97dGfUfbg= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -11,26 +18,44 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= +github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -38,6 +63,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -54,27 +80,52 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index a1876de..1e80e0e 100644 --- a/main.go +++ b/main.go @@ -17,9 +17,6 @@ var ( // Metrics contains all active metrics Metrics = NewMetrics() - // PyCompat enables support for legacy Python templates - PyCompat = false - // version of minitor being run version = "dev" @@ -39,7 +36,7 @@ func sendAlerts(config *Config, monitor *Monitor, alertNotice *AlertNotice) erro } for _, alertName := range alertNames { - if alert, ok := config.Alerts[alertName]; ok { + if alert, ok := config.Alerts.Get(alertName); ok { output, err := alert.Send(*alertNotice) if err != nil { slog.Errorf( @@ -91,7 +88,6 @@ func main() { flag.BoolVar(&slog.DebugLevel, "debug", false, "Enables debug logs (default: false)") flag.BoolVar(&ExportMetrics, "metrics", false, "Enables prometheus metrics exporting (default: false)") - flag.BoolVar(&PyCompat, "py-compat", false, "Enables support for legacy Python Minitor config. Will eventually be removed. (default: false)") flag.IntVar(&MetricsPort, "metrics-port", MetricsPort, "The port that Prometheus metrics should be exported on, if enabled. (default: 8080)") flag.Parse() @@ -120,6 +116,6 @@ func main() { panic(err) } - time.Sleep(config.CheckInterval.Value()) + time.Sleep(config.CheckInterval) } } diff --git a/main_test.go b/main_test.go index 9bc11d7..51418a7 100644 --- a/main_test.go +++ b/main_test.go @@ -18,7 +18,7 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Success", - Command: CommandOrShell{Command: []string{"true"}}, + Command: []string{"true"}, }, }, }, @@ -30,7 +30,7 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Failure", - Command: CommandOrShell{Command: []string{"false"}}, + Command: []string{"false"}, AlertAfter: 1, }, }, @@ -43,7 +43,7 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Success", - Command: CommandOrShell{Command: []string{"ls"}}, + Command: []string{"ls"}, alertCount: 1, }, }, @@ -56,7 +56,7 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Failure", - Command: CommandOrShell{Command: []string{"false"}}, + Command: []string{"false"}, AlertDown: []string{"unknown"}, AlertAfter: 1, }, @@ -70,7 +70,7 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Success", - Command: CommandOrShell{Command: []string{"true"}}, + Command: []string{"true"}, AlertUp: []string{"unknown"}, alertCount: 1, }, @@ -84,15 +84,16 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Failure", - Command: CommandOrShell{Command: []string{"false"}}, + Command: []string{"false"}, AlertDown: []string{"good"}, AlertAfter: 1, }, }, - Alerts: map[string]*Alert{ - "good": { - Command: CommandOrShell{Command: []string{"true"}}, - }, + Alerts: AlertContainer{ + Alerts: []*Alert{{ + Name: "good", + Command: []string{"true"}, + }}, }, }, expectErr: false, @@ -103,16 +104,16 @@ func TestCheckMonitors(t *testing.T) { Monitors: []*Monitor{ { Name: "Failure", - Command: CommandOrShell{Command: []string{"false"}}, + Command: []string{"false"}, AlertDown: []string{"bad"}, AlertAfter: 1, }, }, - Alerts: map[string]*Alert{ - "bad": { + Alerts: AlertContainer{ + Alerts: []*Alert{{ Name: "bad", - Command: CommandOrShell{Command: []string{"false"}}, - }, + Command: []string{"false"}, + }}, }, }, expectErr: true, diff --git a/monitor.go b/monitor.go index a289897..f710aba 100644 --- a/monitor.go +++ b/monitor.go @@ -11,13 +11,14 @@ import ( // Monitor represents a particular periodic check of a command type Monitor struct { //nolint:maligned // Config values - AlertAfter int16 `yaml:"alert_after"` - AlertEvery int16 `yaml:"alert_every"` - CheckInterval SecondsOrDuration `yaml:"check_interval"` - Name string - AlertDown []string `yaml:"alert_down"` - AlertUp []string `yaml:"alert_up"` - Command CommandOrShell + AlertAfter int16 `hcl:"alert_after"` + AlertEvery int16 `hcl:"alert_every"` + CheckInterval time.Duration `hcl:"check_interval"` + Name string `hcl:"name,label"` + AlertDown []string `hcl:"alert_down"` + AlertUp []string `hcl:"alert_up"` + Command []string + ShellCommand string `hcl:"shell_command"` // Other values alertCount int16 @@ -31,7 +32,8 @@ type Monitor struct { //nolint:maligned // IsValid returns a boolean indicating if the Monitor has been correctly // configured func (monitor Monitor) IsValid() bool { - return (!monitor.Command.Empty() && + return ((monitor.Command != nil || monitor.ShellCommand != "") && + !(monitor.Command != nil && monitor.ShellCommand != "") && monitor.getAlertAfter() > 0 && monitor.AlertDown != nil) } @@ -45,17 +47,17 @@ func (monitor Monitor) ShouldCheck() bool { sinceLastCheck := time.Since(monitor.lastCheck) - return sinceLastCheck >= monitor.CheckInterval.Value() + return sinceLastCheck >= monitor.CheckInterval } // Check will run the command configured by the Monitor and return a status // and a possible AlertNotice func (monitor *Monitor) Check() (bool, *AlertNotice) { var cmd *exec.Cmd - if monitor.Command.Command != nil { - cmd = exec.Command(monitor.Command.Command[0], monitor.Command.Command[1:]...) + if monitor.Command != nil { + cmd = exec.Command(monitor.Command[0], monitor.Command[1:]...) } else { - cmd = ShellCommand(monitor.Command.ShellCommand) + cmd = ShellCommand(monitor.ShellCommand) } checkStartTime := time.Now() diff --git a/monitor_test.go b/monitor_test.go index 7d8f7ed..0738109 100644 --- a/monitor_test.go +++ b/monitor_test.go @@ -13,11 +13,11 @@ func TestMonitorIsValid(t *testing.T) { expected bool name string }{ - {Monitor{Command: CommandOrShell{Command: []string{"echo", "test"}}, AlertDown: []string{"log"}}, true, "Command only"}, - {Monitor{Command: CommandOrShell{ShellCommand: "echo test"}, AlertDown: []string{"log"}}, true, "CommandShell only"}, - {Monitor{Command: CommandOrShell{Command: []string{"echo", "test"}}}, false, "No AlertDown"}, + {Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, true, "Command only"}, + {Monitor{ShellCommand: "echo test", AlertDown: []string{"log"}}, true, "CommandShell only"}, + {Monitor{Command: []string{"echo", "test"}}, false, "No AlertDown"}, {Monitor{AlertDown: []string{"log"}}, false, "No commands"}, - {Monitor{Command: CommandOrShell{Command: []string{"echo", "test"}}, AlertDown: []string{"log"}, AlertAfter: -1}, false, "Invalid alert threshold, -1"}, + {Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}, AlertAfter: -1}, false, "Invalid alert threshold, -1"}, } for _, c := range cases { @@ -45,9 +45,9 @@ func TestMonitorShouldCheck(t *testing.T) { name string }{ {Monitor{}, true, "Empty"}, - {Monitor{lastCheck: timeNow, CheckInterval: SecondsOrDuration{time.Second * 15}}, false, "Just checked"}, - {Monitor{lastCheck: timeTenSecAgo, CheckInterval: SecondsOrDuration{time.Second * 15}}, false, "-10s"}, - {Monitor{lastCheck: timeTwentySecAgo, CheckInterval: SecondsOrDuration{time.Second * 15}}, true, "-20s"}, + {Monitor{lastCheck: timeNow, CheckInterval: time.Second * 15}, false, "Just checked"}, + {Monitor{lastCheck: timeTenSecAgo, CheckInterval: time.Second * 15}, false, "-10s"}, + {Monitor{lastCheck: timeTwentySecAgo, CheckInterval: time.Second * 15}, true, "-20s"}, } for _, c := range cases { @@ -267,22 +267,22 @@ func TestMonitorCheck(t *testing.T) { name string }{ { - Monitor{Command: CommandOrShell{Command: []string{"echo", "success"}}}, + Monitor{Command: []string{"echo", "success"}}, expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"}, "Test successful command", }, { - Monitor{Command: CommandOrShell{ShellCommand: "echo success"}}, + Monitor{ShellCommand: "echo success"}, expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"}, "Test successful command shell", }, { - Monitor{Command: CommandOrShell{Command: []string{"total", "failure"}}}, + Monitor{Command: []string{"total", "failure"}}, expected{isSuccess: false, hasNotice: true, lastOutput: ""}, "Test failed command", }, { - Monitor{Command: CommandOrShell{ShellCommand: "false"}}, + Monitor{ShellCommand: "false"}, expected{isSuccess: false, hasNotice: true, lastOutput: ""}, "Test failed command shell", }, diff --git a/sample-config.hcl b/sample-config.hcl new file mode 100644 index 0000000..b54e2e3 --- /dev/null +++ b/sample-config.hcl @@ -0,0 +1,48 @@ +check_interval = "5s" + +monitor "Fake Website" { + command = ["curl", "-s", "-o", "/dev/null", "https://minitor.mon"] + alert_down = ["log_down", "mailgun_down", "sms_down"] + alert_up = ["log_up", "email_up"] + check_interval = "10s" # Must be at minimum the global `check_interval` + alert_after = 3 + alert_every = -1 # Defaults to -1 for exponential backoff. 0 to disable repeating +} + +monitor "Real Website" { + command = ["curl", "-s", "-o", "/dev/null", "https://google.com"] + alert_down = ["log_down", "mailgun_down", "sms_down"] + alert_up = ["log_up", "email_up"] + check_interval = "5s" + alert_after = 3 + alert_every = -1 +} + +alert "log_down" { + command = ["echo", "Minitor failure for {{.MonitorName}}"] + } +alert "log_up" { + command = ["echo", "Minitor recovery for {{.MonitorName}}"] + } +alert "email_up" { + command = ["sendmail", "me@minitor.mon", "Recovered: {monitor_name}", "We're back!"] + } +alert "mailgun_down" { + shell_command = < - echo 'Some string with stuff'; - echo 'another line'; - echo $PATH; - exit 1 - alert_down: ["log_command", "log_shell"] - alert_after: 5 - alert_every: 0 - check_interval: 1m - -alerts: - log_command: - command: ["echo", "regular", '"command!!!"', "{{.MonitorName}}"] - log_shell: - command: echo "Failure on {{.MonitorName}} User is $USER"