diff --git a/alert.go b/alert.go index 428765d..2b78ac6 100644 --- a/alert.go +++ b/alert.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "fmt" "log" "os/exec" "text/template" @@ -36,7 +37,7 @@ func (alert Alert) IsValid() bool { } // BuildTemplates compiles command templates for the Alert -func (alert *Alert) BuildTemplates() { +func (alert *Alert) BuildTemplates() error { if alert.commandTemplate == nil && alert.Command != nil { // build template log.Println("Building template for command...") @@ -52,46 +53,50 @@ func (alert *Alert) BuildTemplates() { alert.commandShellTemplate = template.Must( template.New(alert.Name).Parse(alert.CommandShell), ) - log.Printf("Template built: %v", alert.commandShellTemplate) } else { - log.Fatalf("No template provided for alert %s", alert.Name) + return fmt.Errorf("No template provided for alert %s", alert.Name) } + + return nil } // Send will send an alert notice by executing the command template -func (alert Alert) Send(notice AlertNotice) { +func (alert Alert) Send(notice AlertNotice) (output_str string, err error) { var cmd *exec.Cmd - if alert.commandTemplate != nil { - // build template - log.Println("Send command thing...") command := []string{} for _, cmdTmp := range alert.commandTemplate { var commandBuffer bytes.Buffer - err := cmdTmp.Execute(&commandBuffer, notice) + err = cmdTmp.Execute(&commandBuffer, notice) if err != nil { - panic(err) + return } command = append(command, commandBuffer.String()) } cmd = exec.Command(command[0], command[1:]...) } else if alert.commandShellTemplate != nil { var commandBuffer bytes.Buffer - err := alert.commandShellTemplate.Execute(&commandBuffer, notice) + err = alert.commandShellTemplate.Execute(&commandBuffer, notice) if err != nil { - panic(err) + return } shellCommand := commandBuffer.String() - log.Printf("About to run alert command: %s", shellCommand) cmd = ShellCommand(shellCommand) } else { - panic("No template compiled?") + err = fmt.Errorf("No templates compiled for alert %v", alert.Name) + return } - output, err := cmd.CombinedOutput() - log.Printf("Check %s\n---\n%s\n---", alert.Name, string(output)) - if err != nil { - panic(err) + // Exit if we're not ready to run the command + if cmd == nil || err != nil { + return } + + var output []byte + output, err = cmd.CombinedOutput() + output_str = string(output) + log.Printf("Check %s\n---\n%s\n---", alert.Name, output_str) + + return output_str, err } diff --git a/alert_test.go b/alert_test.go new file mode 100644 index 0000000..8bf3a61 --- /dev/null +++ b/alert_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "log" + "testing" +) + +func TestAlertIsValid(t *testing.T) { + cases := []struct { + alert Alert + expected bool + name string + }{ + {Alert{Command: []string{"echo", "test"}}, true, "Command only"}, + {Alert{CommandShell: "echo test"}, true, "CommandShell only"}, + {Alert{}, false, "No commands"}, + { + Alert{Command: []string{"echo", "test"}, CommandShell: "echo test"}, + false, + "Both commands", + }, + } + + for _, c := range cases { + log.Printf("Testing case %s", c.name) + actual := c.alert.IsValid() + if actual != c.expected { + t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expected, actual) + log.Printf("Case failed: %s", c.name) + } + log.Println("-----") + } +} + +func TestAlertSend(t *testing.T) { + cases := []struct { + alert Alert + notice AlertNotice + expectedOutput string + expectErr bool + name string + }{ + { + Alert{Command: []string{"echo", "{{.MonitorName}}"}}, + AlertNotice{MonitorName: "test"}, + "test\n", + false, + "Command with template", + }, + { + Alert{CommandShell: "echo {{.MonitorName}}"}, + AlertNotice{MonitorName: "test"}, + "test\n", + false, + "Command shell with template", + }, + { + Alert{Command: []string{"echo", "{{.Bad}}"}}, + AlertNotice{MonitorName: "test"}, + "", + true, + "Command with bad template", + }, + { + Alert{CommandShell: "echo {{.Bad}}"}, + AlertNotice{MonitorName: "test"}, + "", + true, + "Command shell with bad template", + }, + } + + for _, c := range cases { + log.Printf("Testing case %s", c.name) + c.alert.BuildTemplates() + output, err := c.alert.Send(c.notice) + hasErr := (err != nil) + if output != c.expectedOutput { + t.Errorf("Send(%v output), expected=%v actual=%v", c.name, c.expectedOutput, output) + log.Printf("Case failed: %s", c.name) + } + if hasErr != c.expectErr { + t.Errorf("Send(%v err), expected=%v actual=%v", c.name, "Err", err) + log.Printf("Case failed: %s", c.name) + } + log.Println("-----") + } +} + +func TestAlertSendNoTemplates(t *testing.T) { + alert := Alert{} + notice := AlertNotice{} + output, err := alert.Send(notice) + if err == nil { + t.Errorf("Send(no template), expected=%v actual=%v", "Err", output) + } + log.Println("-----") +} + +func TestAlertBuildTemplate(t *testing.T) { + cases := []struct { + alert Alert + expectErr bool + name string + }{ + {Alert{Command: []string{"echo", "test"}}, false, "Command only"}, + {Alert{CommandShell: "echo test"}, false, "CommandShell only"}, + {Alert{}, true, "No commands"}, + } + + for _, c := range cases { + log.Printf("Testing case %s", c.name) + err := c.alert.BuildTemplates() + hasErr := (err != nil) + if hasErr != c.expectErr { + t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expectErr, err) + log.Printf("Case failed: %s", c.name) + } + log.Println("-----") + } +} diff --git a/config.go b/config.go index 770619f..a577437 100644 --- a/config.go +++ b/config.go @@ -38,7 +38,9 @@ func (config Config) IsValid() (isValid bool) { func (config *Config) Init() { for name, alert := range config.Alerts { alert.Name = name - alert.BuildTemplates() + if err := alert.BuildTemplates(); err != nil { + panic(err) + } } } diff --git a/main.go b/main.go index befdcf2..6ef9397 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,10 @@ func main() { } for _, alertName := range alerts { if alert, ok := config.Alerts[alertName]; ok { - alert.Send(*alertNotice) + _, err := alert.Send(*alertNotice) + if err != nil { + panic(err) + } } else { log.Printf("WARNING: Could not find alert for %s", alertName) } diff --git a/monitor_test.go b/monitor_test.go index d047b44..9747f33 100644 --- a/monitor_test.go +++ b/monitor_test.go @@ -146,13 +146,13 @@ func TestMonitorFailureAlertEvery(t *testing.T) { name string }{ /* - TODO: Actually found a bug in original implementation. There is an inconsistency in the way AlertAfter is treated. - For "First alert only" (ie. AlertEvery=0), it is the number of failures to ignore before alerting, so AlertAfter=1 - will ignore the first failure and alert on the second failure - For other intervals (ie. AlertEvery=1), it is essentially indexed on one. Essentially making AlertAfter=1 trigger - on the first failure. + TODO: Actually found a bug in original implementation. There is an inconsistency in the way AlertAfter is treated. + For "First alert only" (ie. AlertEvery=0), it is the number of failures to ignore before alerting, so AlertAfter=1 + will ignore the first failure and alert on the second failure + For other intervals (ie. AlertEvery=1), it is essentially indexed on one. Essentially making AlertAfter=1 trigger + on the first failure. - For usabilty, this should be consistent. Consistent with what though? minitor-py? Or itself? Dun dun duuuunnnnn! + For usabilty, this should be consistent. Consistent with what though? minitor-py? Or itself? Dun dun duuuunnnnn! */ {Monitor{AlertAfter: 1}, true, "Empty"}, // Defaults to true because AlertAfter and AlertEvery default to 0 // Alert first time only, after 1