This commit is contained in:
parent
e4dcfeacd5
commit
32fb38fb1e
37
alert.go
37
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}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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"},
|
||||
}
|
||||
|
||||
|
137
config.go
137
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
|
||||
}
|
||||
|
||||
cos.ShellCommand = shellCmd
|
||||
} else {
|
||||
cos.Command = cmd
|
||||
// AlertContainer is struct wrapping map access to alerts
|
||||
type AlertContainer struct {
|
||||
Alerts []*Alert `hcl:"alert,block"`
|
||||
alertLookup map[string]*Alert
|
||||
}
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
v, ok := ac.alertLookup[name]
|
||||
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Value returns a duration value
|
||||
func (sod SecondsOrDuration) Value() time.Duration {
|
||||
return sod.value
|
||||
// IsEmpty checks if there are any defined alerts
|
||||
func (ac AlertContainer) IsEmpty() bool {
|
||||
return ac.Alerts == nil || len(ac.Alerts) == 0
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -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 \"<angle brackets>\"; 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<angle brackets>\n"
|
||||
@ -114,8 +132,13 @@ func TestMultiLineConfig(t *testing.T) {
|
||||
log.Println("TestMultiLineConfig(parse | string)")
|
||||
|
||||
expected = "echo 'Some string with stuff'\necho '<angle brackets>'\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")
|
||||
}
|
||||
|
2
go.mod
2
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
|
||||
)
|
||||
|
57
go.sum
57
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=
|
||||
|
8
main.go
8
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)
|
||||
}
|
||||
}
|
||||
|
31
main_test.go
31
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,
|
||||
|
26
monitor.go
26
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()
|
||||
|
@ -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",
|
||||
},
|
||||
|
48
sample-config.hcl
Normal file
48
sample-config.hcl
Normal file
@ -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 = <<EOF
|
||||
curl -s -X POST
|
||||
-F subject="Alert! {{.MonitorName}} failed"
|
||||
-F from="Minitor <minitor@minitor.mon>"
|
||||
-F to=me@minitor.mon
|
||||
-F text="Our monitor failed"
|
||||
https://api.mailgun.net/v3/minitor.mon/messages
|
||||
-u "api:${MAILGUN_API_KEY}"
|
||||
EOF
|
||||
}
|
||||
alert "sms_down" {
|
||||
shell_command = <<EOF
|
||||
curl -s -X POST -F "Body=Failure! {{.MonitorName}} has failed"
|
||||
-F "From=${AVAILABLE_NUMBER}" -F "To=${MY_PHONE}"
|
||||
"https://api.twilio.com/2010-04-01/Accounts/${ACCOUNT_SID}/Messages"
|
||||
-u "${ACCOUNT_SID}:${AUTH_TOKEN}"
|
||||
EOF
|
||||
}
|
31
test/valid-config.hcl
Normal file
31
test/valid-config.hcl
Normal file
@ -0,0 +1,31 @@
|
||||
check_interval = 1s
|
||||
|
||||
alerts {
|
||||
alert "log_command" {
|
||||
command = ["echo", "regular", "'command!!!'", "{{.MonitorName}}"]
|
||||
}
|
||||
|
||||
alert "log_shell" {
|
||||
shell_command = "echo \"Failure on {{.MonitorName}} User is $USER\""
|
||||
}
|
||||
}
|
||||
|
||||
monitor "Command" {
|
||||
command = ["echo", "$PATH"]
|
||||
alert_down = ["log_command", "log_shell"]
|
||||
alert_every = "0s"
|
||||
check_interval = "10s"
|
||||
}
|
||||
|
||||
monitor "Shell" {
|
||||
shell_command = <<EOF
|
||||
echo 'Some string with stuff';
|
||||
echo 'another line';
|
||||
echo $PATH;
|
||||
exit 1
|
||||
EOF
|
||||
alert_down = ["log_command", "log_shell"]
|
||||
alert_after = 5
|
||||
alert_every = 0
|
||||
check_interval = "1m"
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
---
|
||||
check_interval: 1
|
||||
|
||||
monitors:
|
||||
- name: Command
|
||||
command: ["echo", "$PATH"]
|
||||
alert_down: ["log_command", "log_shell"]
|
||||
alert_every: 0
|
||||
check_interval: 10s
|
||||
- name: 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"
|
Loading…
x
Reference in New Issue
Block a user