Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a39c188a6c | ||
|
|
8f93997b80 | ||
|
|
65342fe0dd | ||
|
|
a7d1b8ab74 | ||
|
|
43ba2914de | ||
|
|
5ed691fdf3 | ||
|
|
0a0f6fe7c9 | ||
|
|
d4e2cb7b9f | ||
|
|
162e8618cb | ||
|
|
c67fe1f4c7 | ||
|
|
5b69eacdd5 | ||
|
|
d6b979f06e | ||
|
|
f4a972747f | ||
|
|
c7c82fabe8 | ||
|
|
f807caa1ad | ||
|
|
3226be69e7 | ||
|
|
0269ad3512 |
+58
-3
@@ -45,15 +45,70 @@ trigger:
|
|||||||
- refs/tags/v*
|
- refs/tags/v*
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: build all binaries
|
||||||
|
image: golang:1.12
|
||||||
|
commands:
|
||||||
|
- make all
|
||||||
|
|
||||||
# Might consider moving this step into the previous pipeline
|
- name: push image - arm
|
||||||
- name: push image
|
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
repo: iamthefij/minitor-go
|
repo: iamthefij/minitor-go
|
||||||
dockerfile: Dockerfile.multi-stage
|
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-arm
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
|
build_args:
|
||||||
|
- ARCH=arm
|
||||||
|
- REPO=arm32v7
|
||||||
|
|
||||||
|
- name: push image - arm64
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: iamthefij/minitor-go
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-arm64
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
build_args:
|
||||||
|
- ARCH=arm64
|
||||||
|
- REPO=arm64v8
|
||||||
|
|
||||||
|
- name: push image - amd64
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: iamthefij/minitor-go
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-amd64
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
|
||||||
|
- name: publish manifest
|
||||||
|
image: plugins/manifest
|
||||||
|
settings:
|
||||||
|
spec: manifest.tmpl
|
||||||
|
auto_tag: true
|
||||||
|
ignore_missing: true
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
|
||||||
|
- name: notify
|
||||||
|
image: drillster/drone-email
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: SMTP_HOST
|
||||||
|
username:
|
||||||
|
from_secret: SMTP_USER
|
||||||
|
password:
|
||||||
|
from_secret: SMTP_PASS
|
||||||
|
from: drone@iamthefij.com
|
||||||
|
when:
|
||||||
|
status: [changed, failure]
|
||||||
|
|||||||
Vendored
+3
-1
@@ -16,4 +16,6 @@
|
|||||||
config.yml
|
config.yml
|
||||||
|
|
||||||
# Output binary
|
# Output binary
|
||||||
minitor-go
|
minitor
|
||||||
|
minitor-linux-*
|
||||||
|
minitor-darwin-amd64
|
||||||
|
|||||||
+9
-4
@@ -1,12 +1,13 @@
|
|||||||
ARG REPO=library
|
ARG REPO=library
|
||||||
|
FROM multiarch/qemu-user-static:4.2.0-2 as qemu-user-static
|
||||||
FROM ${REPO}/alpine:3.10
|
FROM ${REPO}/alpine:3.10
|
||||||
|
|
||||||
|
# Copying all qemu files because amd64 doesn't exist and cannot condional copy
|
||||||
|
COPY --from=qemu-user-static /usr/bin/qemu-* /usr/bin/
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
# Copy minitor in
|
|
||||||
ARG ARCH=amd64
|
|
||||||
COPY ./minitor-go ./minitor
|
|
||||||
|
|
||||||
# Add common checking tools
|
# Add common checking tools
|
||||||
RUN apk --no-cache add bash=~5.0 curl=~7.66 jq=~1.6
|
RUN apk --no-cache add bash=~5.0 curl=~7.66 jq=~1.6
|
||||||
|
|
||||||
@@ -17,6 +18,10 @@ RUN addgroup -S minitor && adduser -S minitor -G minitor
|
|||||||
COPY ./scripts /app/scripts
|
COPY ./scripts /app/scripts
|
||||||
RUN chmod -R 755 /app/scripts
|
RUN chmod -R 755 /app/scripts
|
||||||
|
|
||||||
|
# Copy minitor in
|
||||||
|
ARG ARCH=amd64
|
||||||
|
COPY ./minitor-linux-${ARCH} ./minitor
|
||||||
|
|
||||||
# Drop to non-root user
|
# Drop to non-root user
|
||||||
USER minitor
|
USER minitor
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
.PHONY: all
|
|
||||||
DOCKER_TAG ?= minitor-go-${USER}
|
DOCKER_TAG ?= minitor-go-${USER}
|
||||||
|
GIT_TAG_NAME := $(shell git tag -l --contains HEAD)
|
||||||
|
GIT_SHA := $(shell git rev-parse HEAD)
|
||||||
|
VERSION := $(if $(GIT_TAG_NAME),$(GIT_TAG_NAME),$(GIT_SHA))
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: minitor-linux-amd64 minitor-linux-arm minitor-linux-arm64
|
||||||
|
|
||||||
.PHONY: default
|
.PHONY: default
|
||||||
default: test
|
default: test
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build: minitor
|
||||||
go build
|
|
||||||
|
|
||||||
minitor-go:
|
minitor:
|
||||||
go build
|
@echo Version: $(VERSION)
|
||||||
|
go build -ldflags '-X "main.version=${VERSION}"' -o minitor
|
||||||
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: minitor-go build
|
run: minitor build
|
||||||
./minitor-go -debug
|
./minitor -debug
|
||||||
|
|
||||||
.PHONY: run-metrics
|
.PHONY: run-metrics
|
||||||
run-metrics: minitor-go build
|
run-metrics: minitor build
|
||||||
./minitor-go -debug -metrics
|
./minitor -debug -metrics
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
@@ -41,13 +46,56 @@ check:
|
|||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -f ./minitor-go
|
rm -f ./minitor
|
||||||
|
rm -f ./minitor-linux-*
|
||||||
|
rm -f ./minitor-darwin-amd64
|
||||||
rm -f ./coverage.out
|
rm -f ./coverage.out
|
||||||
|
|
||||||
.PHONY: docker-build
|
.PHONY: docker-build
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build -f ./Dockerfile.multi-stage -t $(DOCKER_TAG) .
|
docker build -f ./Dockerfile.multi-stage -t $(DOCKER_TAG)-linux-amd64 .
|
||||||
|
|
||||||
.PHONY: docker-run
|
.PHONY: docker-run
|
||||||
docker-run: docker-build
|
docker-run: docker-build
|
||||||
docker run --rm -v $(shell pwd)/config.yml:/root/config.yml $(DOCKER_TAG)
|
docker run --rm -v $(shell pwd)/config.yml:/root/config.yml $(DOCKER_TAG)
|
||||||
|
|
||||||
|
## Multi-arch targets
|
||||||
|
|
||||||
|
# Arch specific go build targets
|
||||||
|
minitor-darwin-amd64:
|
||||||
|
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=${VERSION}"' -a -installsuffix nocgo \
|
||||||
|
-o minitor-darwin-amd64
|
||||||
|
|
||||||
|
minitor-linux-amd64:
|
||||||
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=${VERSION}"' -a -installsuffix nocgo \
|
||||||
|
-o minitor-linux-amd64
|
||||||
|
|
||||||
|
minitor-linux-arm:
|
||||||
|
GOOS=linux GOARCH=arm CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=${VERSION}"' -a -installsuffix nocgo \
|
||||||
|
-o minitor-linux-arm
|
||||||
|
|
||||||
|
minitor-linux-arm64:
|
||||||
|
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=${VERSION}"' -a -installsuffix nocgo \
|
||||||
|
-o minitor-linux-arm64
|
||||||
|
|
||||||
|
# Arch specific docker build targets
|
||||||
|
.PHONY: docker-build-arm
|
||||||
|
docker-build-arm: minitor-linux-arm
|
||||||
|
docker build --build-arg REPO=arm32v7 --build-arg ARCH=arm . -t ${DOCKER_TAG}-linux-arm
|
||||||
|
|
||||||
|
.PHONY: docker-build-arm
|
||||||
|
docker-build-arm64: minitor-linux-arm64
|
||||||
|
docker build --build-arg REPO=arm64v8 --build-arg ARCH=arm64 . -t ${DOCKER_TAG}-linux-arm64
|
||||||
|
|
||||||
|
# Cross run on host architechture
|
||||||
|
.PHONY: docker-run-arm
|
||||||
|
docker-run-arm: docker-build-arm
|
||||||
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run ${DOCKER_TAG}-linux-arm
|
||||||
|
|
||||||
|
.PHONY: docker-run-arm64
|
||||||
|
docker-run-arm64: docker-build-arm64
|
||||||
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run ${DOCKER_TAG}-linux-arm64
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# minitor-go
|
# minitor-go
|
||||||
|
|
||||||
A reimplementation of [Minitor](https://git.iamthefij/iamthefij/minitor) in Go
|
A reimplementation of [Minitor](https://git.iamthefij.com/iamthefij/minitor) in Go
|
||||||
|
|
||||||
Minitor is already a minimal monitoring tool. Python 3 was a quick way to get something live, but Python itself comes with a large footprint. Thus Go feels like a better fit for the project, longer term.
|
Minitor is already a minimal monitoring tool. Python 3 was a quick way to get something live, but Python itself comes with a large footprint. Thus Go feels like a better fit for the project, longer term.
|
||||||
|
|
||||||
@@ -8,29 +8,8 @@ Initial target is meant to be roughly compatible requiring only minor changes to
|
|||||||
|
|
||||||
## Differences from Python version
|
## Differences from Python version
|
||||||
|
|
||||||
There are a few key differences between the Python version and the v0.x Go version.
|
|
||||||
|
|
||||||
First, configuration keys cannot have multiple types in Go, so a different key must be used when specifying a Shell command as a string rather than a list of args. Instead of `command`, you must use `command_shell`. Eg:
|
Templating for Alert messages has been updated. In the Python version, `str.format(...)` was used with certain keys passed in that could be used to format messages. In the Go version, we use a struct, `AlertNotice` defined in `alert.go` and the built in Go templating format. Eg.
|
||||||
|
|
||||||
minitor-py:
|
|
||||||
```yaml
|
|
||||||
monitors:
|
|
||||||
- name: Exec command
|
|
||||||
command: ['echo', 'test']
|
|
||||||
- name: Shell command
|
|
||||||
command: echo 'test'
|
|
||||||
```
|
|
||||||
|
|
||||||
minitor-go:
|
|
||||||
```yaml
|
|
||||||
monitors:
|
|
||||||
- name: Exec command
|
|
||||||
command: ['echo', 'test']
|
|
||||||
- name: Shell command
|
|
||||||
command_shell: echo 'test'
|
|
||||||
```
|
|
||||||
|
|
||||||
Second, templating for Alert messages has been updated. In the Python version, `str.format(...)` was used with certain keys passed in that could be used to format messages. In the Go version, we use a struct, `AlertNotice` defined in `alert.go` and the built in Go templating format. Eg.
|
|
||||||
|
|
||||||
minitor-py:
|
minitor-py:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -38,7 +17,7 @@ alerts:
|
|||||||
log_command:
|
log_command:
|
||||||
command: ['echo', '{monitor_name}']
|
command: ['echo', '{monitor_name}']
|
||||||
log_shell:
|
log_shell:
|
||||||
command_shell: 'echo {monitor_name}'
|
command: 'echo {monitor_name}'
|
||||||
```
|
```
|
||||||
|
|
||||||
minitor-go:
|
minitor-go:
|
||||||
@@ -47,7 +26,7 @@ alerts:
|
|||||||
log_command:
|
log_command:
|
||||||
command: ['echo', '{{.MonitorName}}']
|
command: ['echo', '{{.MonitorName}}']
|
||||||
log_shell:
|
log_shell:
|
||||||
command_shell: 'echo {{.MonitorName}}'
|
command: 'echo {{.MonitorName}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, newlines in a shell command don't terminate a particular command. Semicolons must be used and continuations should not.
|
Finally, newlines in a shell command don't terminate a particular command. Semicolons must be used and continuations should not.
|
||||||
@@ -56,7 +35,7 @@ minitor-py:
|
|||||||
```yaml
|
```yaml
|
||||||
alerts:
|
alerts:
|
||||||
log_shell:
|
log_shell:
|
||||||
command_shell: >
|
command: >
|
||||||
echo "line 1"
|
echo "line 1"
|
||||||
echo "line 2"
|
echo "line 2"
|
||||||
echo "continued" \
|
echo "continued" \
|
||||||
@@ -67,7 +46,7 @@ minitor-go:
|
|||||||
```yaml
|
```yaml
|
||||||
alerts:
|
alerts:
|
||||||
log_shell:
|
log_shell:
|
||||||
command_shell: >
|
command: >
|
||||||
echo "line 1";
|
echo "line 1";
|
||||||
echo "line 2";
|
echo "line 2";
|
||||||
echo "continued"
|
echo "continued"
|
||||||
@@ -86,7 +65,8 @@ Pairity:
|
|||||||
- [x] Allow templating of alert commands
|
- [x] Allow templating of alert commands
|
||||||
- [x] Implement Prometheus client to export metrics
|
- [x] Implement Prometheus client to export metrics
|
||||||
- [x] Test coverage
|
- [x] Test coverage
|
||||||
- [ ] Integration testing (manual or otherwise)
|
- [x] Integration testing (manual or otherwise)
|
||||||
|
- [x] Allow commands and shell commands in the same config key
|
||||||
|
|
||||||
Improvement (potentially breaking):
|
Improvement (potentially breaking):
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alert is a config driven mechanism for sending a notice
|
// Alert is a config driven mechanism for sending a notice
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
Name string
|
Name string
|
||||||
Command []string
|
Command CommandOrShell
|
||||||
CommandShell string `yaml:"command_shell"`
|
|
||||||
commandTemplate []*template.Template
|
commandTemplate []*template.Template
|
||||||
commandShellTemplate *template.Template
|
commandShellTemplate *template.Template
|
||||||
}
|
}
|
||||||
@@ -32,24 +31,40 @@ type AlertNotice struct {
|
|||||||
// IsValid returns a boolean indicating if the Alert has been correctly
|
// IsValid returns a boolean indicating if the Alert has been correctly
|
||||||
// configured
|
// configured
|
||||||
func (alert Alert) IsValid() bool {
|
func (alert Alert) IsValid() bool {
|
||||||
atLeastOneCommand := (alert.CommandShell != "" || alert.Command != nil)
|
return !alert.Command.Empty()
|
||||||
atMostOneCommand := (alert.CommandShell == "" || alert.Command == nil)
|
|
||||||
return atLeastOneCommand && atMostOneCommand
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildTemplates compiles command templates for the Alert
|
// BuildTemplates compiles command templates for the Alert
|
||||||
func (alert *Alert) BuildTemplates() error {
|
func (alert *Alert) BuildTemplates() error {
|
||||||
log.Debugf("Building template for alert %s", alert.Name)
|
// TODO: Remove legacy template support later after 1.0
|
||||||
if alert.commandTemplate == nil && alert.Command != nil {
|
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}}",
|
||||||
|
)
|
||||||
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Building template for alert %s", alert.Name)
|
||||||
|
}
|
||||||
|
if alert.commandTemplate == nil && alert.Command.Command != nil {
|
||||||
alert.commandTemplate = []*template.Template{}
|
alert.commandTemplate = []*template.Template{}
|
||||||
for i, cmdPart := range alert.Command {
|
for i, cmdPart := range alert.Command.Command {
|
||||||
|
if PyCompat {
|
||||||
|
cmdPart = legacy.Replace(cmdPart)
|
||||||
|
}
|
||||||
alert.commandTemplate = append(alert.commandTemplate, template.Must(
|
alert.commandTemplate = append(alert.commandTemplate, template.Must(
|
||||||
template.New(alert.Name+string(i)).Parse(cmdPart),
|
template.New(alert.Name+string(i)).Parse(cmdPart),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else if alert.commandShellTemplate == nil && alert.CommandShell != "" {
|
} else if alert.commandShellTemplate == nil && alert.Command.ShellCommand != "" {
|
||||||
|
shellCmd := alert.Command.ShellCommand
|
||||||
|
if PyCompat {
|
||||||
|
shellCmd = legacy.Replace(shellCmd)
|
||||||
|
}
|
||||||
alert.commandShellTemplate = template.Must(
|
alert.commandShellTemplate = template.Must(
|
||||||
template.New(alert.Name).Parse(alert.CommandShell),
|
template.New(alert.Name).Parse(shellCmd),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("No template provided for alert %s", alert.Name)
|
return fmt.Errorf("No template provided for alert %s", alert.Name)
|
||||||
@@ -60,7 +75,7 @@ func (alert *Alert) BuildTemplates() error {
|
|||||||
|
|
||||||
// Send will send an alert notice by executing the command template
|
// Send will send an alert notice by executing the command template
|
||||||
func (alert Alert) Send(notice AlertNotice) (outputStr string, err error) {
|
func (alert Alert) Send(notice AlertNotice) (outputStr string, err error) {
|
||||||
log.Infof("Sending alert %s for %s", alert.Name, notice.MonitorName)
|
log.Printf("INFO: Sending alert %s for %s", alert.Name, notice.MonitorName)
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if alert.commandTemplate != nil {
|
if alert.commandTemplate != nil {
|
||||||
command := []string{}
|
command := []string{}
|
||||||
@@ -95,7 +110,22 @@ func (alert Alert) Send(notice AlertNotice) (outputStr string, err error) {
|
|||||||
var output []byte
|
var output []byte
|
||||||
output, err = cmd.CombinedOutput()
|
output, err = cmd.CombinedOutput()
|
||||||
outputStr = string(output)
|
outputStr = string(output)
|
||||||
log.Debugf("Alert output for: %s\n---\n%s\n---", alert.Name, outputStr)
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Alert output for: %s\n---\n%s\n---", alert.Name, outputStr)
|
||||||
|
}
|
||||||
|
|
||||||
return outputStr, err
|
return outputStr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLogAlert creates an alert that does basic logging using echo
|
||||||
|
func NewLogAlert() *Alert {
|
||||||
|
return &Alert{
|
||||||
|
Name: "log",
|
||||||
|
Command: CommandOrShell{
|
||||||
|
Command: []string{
|
||||||
|
"echo",
|
||||||
|
"{{.MonitorName}} check has failed {{.FailureCount}} times",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+37
-26
@@ -1,9 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAlertIsValid(t *testing.T) {
|
func TestAlertIsValid(t *testing.T) {
|
||||||
@@ -12,24 +11,19 @@ func TestAlertIsValid(t *testing.T) {
|
|||||||
expected bool
|
expected bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Alert{Command: []string{"echo", "test"}}, true, "Command only"},
|
{Alert{Command: CommandOrShell{Command: []string{"echo", "test"}}}, true, "Command only"},
|
||||||
{Alert{CommandShell: "echo test"}, true, "CommandShell only"},
|
{Alert{Command: CommandOrShell{ShellCommand: "echo test"}}, true, "CommandShell only"},
|
||||||
{Alert{}, false, "No commands"},
|
{Alert{}, false, "No commands"},
|
||||||
{
|
|
||||||
Alert{Command: []string{"echo", "test"}, CommandShell: "echo test"},
|
|
||||||
false,
|
|
||||||
"Both commands",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
log.Debugf("Testing case %s", c.name)
|
log.Printf("Testing case %s", c.name)
|
||||||
actual := c.alert.IsValid()
|
actual := c.alert.IsValid()
|
||||||
if actual != c.expected {
|
if actual != c.expected {
|
||||||
t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expected, actual)
|
t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expected, actual)
|
||||||
log.Debugf("Case failed: %s", c.name)
|
log.Printf("Case failed: %s", c.name)
|
||||||
}
|
}
|
||||||
log.Debugf("-----")
|
log.Println("-----")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,51 +34,68 @@ func TestAlertSend(t *testing.T) {
|
|||||||
expectedOutput string
|
expectedOutput string
|
||||||
expectErr bool
|
expectErr bool
|
||||||
name string
|
name string
|
||||||
|
pyCompat bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Alert{Command: []string{"echo", "{{.MonitorName}}"}},
|
Alert{Command: CommandOrShell{Command: []string{"echo", "{{.MonitorName}}"}}},
|
||||||
AlertNotice{MonitorName: "test"},
|
AlertNotice{MonitorName: "test"},
|
||||||
"test\n",
|
"test\n",
|
||||||
false,
|
false,
|
||||||
"Command with template",
|
"Command with template",
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{CommandShell: "echo {{.MonitorName}}"},
|
Alert{Command: CommandOrShell{ShellCommand: "echo {{.MonitorName}}"}},
|
||||||
AlertNotice{MonitorName: "test"},
|
AlertNotice{MonitorName: "test"},
|
||||||
"test\n",
|
"test\n",
|
||||||
false,
|
false,
|
||||||
"Command shell with template",
|
"Command shell with template",
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{Command: []string{"echo", "{{.Bad}}"}},
|
Alert{Command: CommandOrShell{Command: []string{"echo", "{{.Bad}}"}}},
|
||||||
AlertNotice{MonitorName: "test"},
|
AlertNotice{MonitorName: "test"},
|
||||||
"",
|
"",
|
||||||
true,
|
true,
|
||||||
"Command with bad template",
|
"Command with bad template",
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Alert{CommandShell: "echo {{.Bad}}"},
|
Alert{Command: CommandOrShell{ShellCommand: "echo {{.Bad}}"}},
|
||||||
AlertNotice{MonitorName: "test"},
|
AlertNotice{MonitorName: "test"},
|
||||||
"",
|
"",
|
||||||
true,
|
true,
|
||||||
"Command shell with bad template",
|
"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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
log.Debugf("Testing case %s", c.name)
|
log.Printf("Testing case %s", c.name)
|
||||||
|
// Set PyCompat to value of compat flag
|
||||||
|
PyCompat = c.pyCompat
|
||||||
c.alert.BuildTemplates()
|
c.alert.BuildTemplates()
|
||||||
output, err := c.alert.Send(c.notice)
|
output, err := c.alert.Send(c.notice)
|
||||||
hasErr := (err != nil)
|
hasErr := (err != nil)
|
||||||
if output != c.expectedOutput {
|
if output != c.expectedOutput {
|
||||||
t.Errorf("Send(%v output), expected=%v actual=%v", c.name, c.expectedOutput, output)
|
t.Errorf("Send(%v output), expected=%v actual=%v", c.name, c.expectedOutput, output)
|
||||||
log.Debugf("Case failed: %s", c.name)
|
log.Printf("Case failed: %s", c.name)
|
||||||
}
|
}
|
||||||
if hasErr != c.expectErr {
|
if hasErr != c.expectErr {
|
||||||
t.Errorf("Send(%v err), expected=%v actual=%v", c.name, "Err", err)
|
t.Errorf("Send(%v err), expected=%v actual=%v", c.name, "Err", err)
|
||||||
log.Debugf("Case failed: %s", c.name)
|
log.Printf("Case failed: %s", c.name)
|
||||||
}
|
}
|
||||||
log.Debugf("-----")
|
// Set PyCompat back to default value
|
||||||
|
PyCompat = false
|
||||||
|
log.Println("-----")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +106,7 @@ func TestAlertSendNoTemplates(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Send(no template), expected=%v actual=%v", "Err", output)
|
t.Errorf("Send(no template), expected=%v actual=%v", "Err", output)
|
||||||
}
|
}
|
||||||
log.Debugf("-----")
|
log.Println("-----")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertBuildTemplate(t *testing.T) {
|
func TestAlertBuildTemplate(t *testing.T) {
|
||||||
@@ -104,19 +115,19 @@ func TestAlertBuildTemplate(t *testing.T) {
|
|||||||
expectErr bool
|
expectErr bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Alert{Command: []string{"echo", "test"}}, false, "Command only"},
|
{Alert{Command: CommandOrShell{Command: []string{"echo", "test"}}}, false, "Command only"},
|
||||||
{Alert{CommandShell: "echo test"}, false, "CommandShell only"},
|
{Alert{Command: CommandOrShell{ShellCommand: "echo test"}}, false, "CommandShell only"},
|
||||||
{Alert{}, true, "No commands"},
|
{Alert{}, true, "No commands"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
log.Debugf("Testing case %s", c.name)
|
log.Printf("Testing case %s", c.name)
|
||||||
err := c.alert.BuildTemplates()
|
err := c.alert.BuildTemplates()
|
||||||
hasErr := (err != nil)
|
hasErr := (err != nil)
|
||||||
if hasErr != c.expectErr {
|
if hasErr != c.expectErr {
|
||||||
t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expectErr, err)
|
t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expectErr, err)
|
||||||
log.Debugf("Case failed: %s", c.name)
|
log.Printf("Case failed: %s", c.name)
|
||||||
}
|
}
|
||||||
log.Debugf("-----")
|
log.Println("-----")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"log"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,26 +15,70 @@ type Config struct {
|
|||||||
Alerts map[string]*Alert
|
Alerts map[string]*Alert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty checks if the Command has a value
|
||||||
|
func (cos CommandOrShell) Empty() bool {
|
||||||
|
return (cos.ShellCommand == "" && cos.Command == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if err != nil {
|
||||||
|
var shellCmd string
|
||||||
|
err := unmarshal(&shellCmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cos.ShellCommand = shellCmd
|
||||||
|
} else {
|
||||||
|
cos.Command = cmd
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsValid checks config validity and returns true if valid
|
// IsValid checks config validity and returns true if valid
|
||||||
func (config Config) IsValid() (isValid bool) {
|
func (config Config) IsValid() (isValid bool) {
|
||||||
isValid = true
|
isValid = true
|
||||||
|
|
||||||
|
// Validate alerts
|
||||||
|
if config.Alerts == nil || len(config.Alerts) == 0 {
|
||||||
|
// This should never happen because there is a default alert named 'log' for now
|
||||||
|
log.Printf("ERROR: Invalid alert configuration: Must provide at least one alert")
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
for _, alert := range config.Alerts {
|
||||||
|
if !alert.IsValid() {
|
||||||
|
log.Printf("ERROR: Invalid alert configuration: %s", alert.Name)
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate monitors
|
// Validate monitors
|
||||||
if config.Monitors == nil || len(config.Monitors) == 0 {
|
if config.Monitors == nil || len(config.Monitors) == 0 {
|
||||||
log.Errorf("Invalid monitor configuration: Must provide at least one monitor")
|
log.Printf("ERROR: Invalid monitor configuration: Must provide at least one monitor")
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
for _, monitor := range config.Monitors {
|
for _, monitor := range config.Monitors {
|
||||||
if !monitor.IsValid() {
|
if !monitor.IsValid() {
|
||||||
log.Errorf("Invalid monitor configuration: %s", monitor.Name)
|
log.Printf("ERROR: Invalid monitor configuration: %s", monitor.Name)
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
// Check that all Monitor alerts actually exist
|
// Check that all Monitor alerts actually exist
|
||||||
for _, isUp := range []bool{true, false} {
|
for _, isUp := range []bool{true, false} {
|
||||||
for _, alertName := range monitor.GetAlertNames(isUp) {
|
for _, alertName := range monitor.GetAlertNames(isUp) {
|
||||||
if _, ok := config.Alerts[alertName]; !ok {
|
if _, ok := config.Alerts[alertName]; !ok {
|
||||||
log.Errorf(
|
log.Printf(
|
||||||
"Invalid monitor configuration: %s. Unknown alert %s",
|
"ERROR: Invalid monitor configuration: %s. Unknown alert %s",
|
||||||
monitor.Name, alertName,
|
monitor.Name, alertName,
|
||||||
)
|
)
|
||||||
isValid = false
|
isValid = false
|
||||||
@@ -44,18 +87,6 @@ func (config Config) IsValid() (isValid bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate alerts
|
|
||||||
if config.Alerts == nil || len(config.Alerts) == 0 {
|
|
||||||
log.Errorf("Invalid alert configuration: Must provide at least one alert")
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
for _, alert := range config.Alerts {
|
|
||||||
if !alert.IsValid() {
|
|
||||||
log.Errorf("Invalid alert configuration: %s", alert.Name)
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,14 +109,25 @@ func LoadConfig(filePath string) (config Config, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Decide if this is better expanded here, or only when executing
|
err = yaml.Unmarshal(data, &config)
|
||||||
envExpanded := os.ExpandEnv(string(data))
|
|
||||||
err = yaml.Unmarshal([]byte(envExpanded), &config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Config values:\n%v\n", config)
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Config values:\n%v\n", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add log alert if not present
|
||||||
|
if PyCompat {
|
||||||
|
// Intialize alerts list if not present
|
||||||
|
if config.Alerts == nil {
|
||||||
|
config.Alerts = map[string]*Alert{}
|
||||||
|
}
|
||||||
|
if _, ok := config.Alerts["log"]; !ok {
|
||||||
|
config.Alerts["log"] = NewLogAlert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !config.IsValid() {
|
if !config.IsValid() {
|
||||||
err = errors.New("Invalid configuration")
|
err = errors.New("Invalid configuration")
|
||||||
|
|||||||
+78
-6
@@ -10,22 +10,94 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
configPath string
|
configPath string
|
||||||
expectErr bool
|
expectErr bool
|
||||||
name string
|
name string
|
||||||
|
pyCompat bool
|
||||||
}{
|
}{
|
||||||
{"./test/valid-config.yml", false, "Valid config file"},
|
{"./test/valid-config.yml", false, "Valid config file", false},
|
||||||
{"./test/does-not-exist", true, "Invalid config path"},
|
{"./test/valid-default-log-alert.yml", false, "Valid config file with default log alert PyCompat", true},
|
||||||
{"./test/invalid-config-type.yml", true, "Invalid config type for key"},
|
{"./test/valid-default-log-alert.yml", true, "Invalid config file no log alert", false},
|
||||||
{"./test/invalid-config-missing-alerts.yml", true, "Invalid config missing alerts"},
|
{"./test/does-not-exist", true, "Invalid config path", false},
|
||||||
{"./test/invalid-config-unknown-alert.yml", true, "Invalid config unknown alert"},
|
{"./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},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
log.Printf("Testing case %s", c.name)
|
log.Printf("Testing case %s", c.name)
|
||||||
|
// Set PyCompat based on compatibility mode
|
||||||
|
PyCompat = c.pyCompat
|
||||||
_, err := LoadConfig(c.configPath)
|
_, err := LoadConfig(c.configPath)
|
||||||
hasErr := (err != nil)
|
hasErr := (err != nil)
|
||||||
if hasErr != c.expectErr {
|
if hasErr != c.expectErr {
|
||||||
t.Errorf("LoadConfig(%v), expected=%v actual=%v", c.name, "Err", err)
|
t.Errorf("LoadConfig(%v), expected_error=%v actual=%v", c.name, c.expectErr, err)
|
||||||
log.Printf("Case failed: %s", c.name)
|
log.Printf("Case failed: %s", c.name)
|
||||||
}
|
}
|
||||||
|
// Set PyCompat to default value
|
||||||
|
PyCompat = false
|
||||||
log.Println("-----")
|
log.Println("-----")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMultiLineConfig is a more complicated test stepping through the parsing
|
||||||
|
// and execution of mutli-line strings presented in YAML
|
||||||
|
func TestMultiLineConfig(t *testing.T) {
|
||||||
|
log.Println("Testing multi-line string config")
|
||||||
|
config, err := LoadConfig("./test/valid-verify-multi-line.yml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestMultiLineConfig(load), expected=no_error actual=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("-----")
|
||||||
|
log.Println("TestMultiLineConfig(parse > string)")
|
||||||
|
expected := "echo 'Some string with stuff'; echo \"<angle brackets>\"; exit 1\n"
|
||||||
|
actual := config.Monitors[0].Command.ShellCommand
|
||||||
|
if expected != actual {
|
||||||
|
t.Errorf("TestMultiLineConfig(>) failed")
|
||||||
|
t.Logf("string expected=`%v`", expected)
|
||||||
|
t.Logf("string actual =`%v`", actual)
|
||||||
|
t.Logf("bytes expected=%v", []byte(expected))
|
||||||
|
t.Logf("bytes actual =%v", []byte(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("-----")
|
||||||
|
log.Println("TestMultiLineConfig(execute > string)")
|
||||||
|
_, notice := config.Monitors[0].Check()
|
||||||
|
if notice == nil {
|
||||||
|
t.Fatalf("Did not receive an alert notice")
|
||||||
|
}
|
||||||
|
expected = "Some string with stuff\n<angle brackets>\n"
|
||||||
|
actual = notice.LastCheckOutput
|
||||||
|
if expected != actual {
|
||||||
|
t.Errorf("TestMultiLineConfig(execute > string) check failed")
|
||||||
|
t.Logf("string expected=`%v`", expected)
|
||||||
|
t.Logf("string actual =`%v`", actual)
|
||||||
|
t.Logf("bytes expected=%v", []byte(expected))
|
||||||
|
t.Logf("bytes actual =%v", []byte(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("-----")
|
||||||
|
log.Println("TestMultiLineConfig(parse | string)")
|
||||||
|
expected = "echo 'Some string with stuff'\necho '<angle brackets>'\n"
|
||||||
|
actual = config.Alerts["log_shell"].Command.ShellCommand
|
||||||
|
if expected != actual {
|
||||||
|
t.Errorf("TestMultiLineConfig(|) failed")
|
||||||
|
t.Logf("string expected=`%v`", expected)
|
||||||
|
t.Logf("string actual =`%v`", actual)
|
||||||
|
t.Logf("bytes expected=%v", []byte(expected))
|
||||||
|
t.Logf("bytes actual =%v", []byte(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("-----")
|
||||||
|
log.Println("TestMultiLineConfig(execute | string)")
|
||||||
|
actual, err = config.Alerts["log_shell"].Send(AlertNotice{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Execution of alert failed")
|
||||||
|
}
|
||||||
|
expected = "Some string with stuff\n<angle brackets>\n"
|
||||||
|
if expected != actual {
|
||||||
|
t.Errorf("TestMultiLineConfig(execute | string) check failed")
|
||||||
|
t.Logf("string expected=`%v`", expected)
|
||||||
|
t.Logf("string actual =`%v`", actual)
|
||||||
|
t.Logf("bytes expected=%v", []byte(expected))
|
||||||
|
t.Logf("bytes actual =%v", []byte(actual))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ go 1.12
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/prometheus/client_golang v1.2.1
|
github.com/prometheus/client_golang v1.2.1
|
||||||
github.com/sirupsen/logrus v1.4.2
|
|
||||||
gopkg.in/yaml.v2 v2.2.4
|
gopkg.in/yaml.v2 v2.2.4
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
|||||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
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/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -69,7 +68,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// LogDebug will control whether debug messsages should be logged
|
||||||
|
LogDebug = false
|
||||||
|
|
||||||
// ExportMetrics will track whether or not we want to export metrics to prometheus
|
// ExportMetrics will track whether or not we want to export metrics to prometheus
|
||||||
ExportMetrics = false
|
ExportMetrics = false
|
||||||
// MetricsPort is the port to expose metrics on
|
// MetricsPort is the port to expose metrics on
|
||||||
@@ -16,6 +18,9 @@ var (
|
|||||||
// Metrics contains all active metrics
|
// Metrics contains all active metrics
|
||||||
Metrics = NewMetrics()
|
Metrics = NewMetrics()
|
||||||
|
|
||||||
|
// PyCompat enables support for legacy Python templates
|
||||||
|
PyCompat = false
|
||||||
|
|
||||||
// version of minitor being run
|
// version of minitor being run
|
||||||
version = "dev"
|
version = "dev"
|
||||||
)
|
)
|
||||||
@@ -33,7 +38,9 @@ func checkMonitors(config *Config) error {
|
|||||||
|
|
||||||
// Should probably consider refactoring everything below here
|
// Should probably consider refactoring everything below here
|
||||||
if alertNotice != nil {
|
if alertNotice != nil {
|
||||||
log.Debugf("Recieved an alert notice from %s", alertNotice.MonitorName)
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Recieved an alert notice from %s", alertNotice.MonitorName)
|
||||||
|
}
|
||||||
alertNames := monitor.GetAlertNames(alertNotice.IsUp)
|
alertNames := monitor.GetAlertNames(alertNotice.IsUp)
|
||||||
if alertNames == nil {
|
if alertNames == nil {
|
||||||
// This should only happen for a recovery alert. AlertDown is validated not empty
|
// This should only happen for a recovery alert. AlertDown is validated not empty
|
||||||
@@ -77,16 +84,13 @@ func checkMonitors(config *Config) error {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Get debug flag
|
// Get debug flag
|
||||||
var debug = flag.Bool("debug", false, "Enables debug logs (default: false)")
|
flag.BoolVar(&LogDebug, "debug", false, "Enables debug logs (default: false)")
|
||||||
flag.BoolVar(&ExportMetrics, "metrics", false, "Enables prometheus metrics exporting (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)")
|
||||||
var showVersion = flag.Bool("version", false, "Display the version of minitor and exit")
|
var showVersion = flag.Bool("version", false, "Display the version of minitor and exit")
|
||||||
|
var configPath = flag.String("config", "config.yml", "Alternate configuration path (default: config.yml)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Set debug if flag is set
|
|
||||||
if *debug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print version if flag is provided
|
// Print version if flag is provided
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
log.Println("Minitor version:", version)
|
log.Println("Minitor version:", version)
|
||||||
@@ -94,7 +98,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
config, err := LoadConfig("config.yml")
|
config, err := LoadConfig(*configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error loading config: %v", err)
|
log.Fatalf("Error loading config: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -18,7 +18,7 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Monitors: []*Monitor{
|
Monitors: []*Monitor{
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Success",
|
Name: "Success",
|
||||||
Command: []string{"true"},
|
Command: CommandOrShell{Command: []string{"true"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -30,12 +30,12 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Monitors: []*Monitor{
|
Monitors: []*Monitor{
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
Command: CommandOrShell{Command: []string{"false"}},
|
||||||
AlertAfter: 1,
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
Command: CommandOrShell{Command: []string{"false"}},
|
||||||
AlertDown: []string{"unknown"},
|
AlertDown: []string{"unknown"},
|
||||||
AlertAfter: 1,
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
@@ -49,12 +49,12 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Monitors: []*Monitor{
|
Monitors: []*Monitor{
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Success",
|
Name: "Success",
|
||||||
Command: []string{"ls"},
|
Command: CommandOrShell{Command: []string{"ls"}},
|
||||||
alertCount: 1,
|
alertCount: 1,
|
||||||
},
|
},
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Success",
|
Name: "Success",
|
||||||
Command: []string{"true"},
|
Command: CommandOrShell{Command: []string{"true"}},
|
||||||
AlertUp: []string{"unknown"},
|
AlertUp: []string{"unknown"},
|
||||||
alertCount: 1,
|
alertCount: 1,
|
||||||
},
|
},
|
||||||
@@ -68,14 +68,14 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Monitors: []*Monitor{
|
Monitors: []*Monitor{
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
Command: CommandOrShell{Command: []string{"false"}},
|
||||||
AlertDown: []string{"good"},
|
AlertDown: []string{"good"},
|
||||||
AlertAfter: 1,
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Alerts: map[string]*Alert{
|
Alerts: map[string]*Alert{
|
||||||
"good": &Alert{
|
"good": &Alert{
|
||||||
Command: []string{"true"},
|
Command: CommandOrShell{Command: []string{"true"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Monitors: []*Monitor{
|
Monitors: []*Monitor{
|
||||||
&Monitor{
|
&Monitor{
|
||||||
Name: "Failure",
|
Name: "Failure",
|
||||||
Command: []string{"false"},
|
Command: CommandOrShell{Command: []string{"false"}},
|
||||||
AlertDown: []string{"bad"},
|
AlertDown: []string{"bad"},
|
||||||
AlertAfter: 1,
|
AlertAfter: 1,
|
||||||
},
|
},
|
||||||
@@ -95,7 +95,7 @@ func TestCheckMonitors(t *testing.T) {
|
|||||||
Alerts: map[string]*Alert{
|
Alerts: map[string]*Alert{
|
||||||
"bad": &Alert{
|
"bad": &Alert{
|
||||||
Name: "bad",
|
Name: "bad",
|
||||||
Command: []string{"false"},
|
Command: CommandOrShell{Command: []string{"false"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
image: iamthefij/minitor-go:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||||
|
{{#if build.tags}}
|
||||||
|
tags:
|
||||||
|
{{#each build.tags}}
|
||||||
|
- {{this}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
manifests:
|
||||||
|
-
|
||||||
|
image: iamthefij/minitor-go:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||||
|
platform:
|
||||||
|
architecture: amd64
|
||||||
|
os: linux
|
||||||
|
-
|
||||||
|
image: iamthefij/minitor-go:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||||
|
platform:
|
||||||
|
architecture: arm64
|
||||||
|
os: linux
|
||||||
|
variant: v8
|
||||||
|
-
|
||||||
|
image: iamthefij/minitor-go:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||||
|
platform:
|
||||||
|
architecture: arm
|
||||||
|
os: linux
|
||||||
|
variant: v7
|
||||||
+20
-21
@@ -1,19 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Monitor represents a particular periodic check of a command
|
// Monitor represents a particular periodic check of a command
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
// Config values
|
// Config values
|
||||||
Name string
|
Name string
|
||||||
Command []string
|
Command CommandOrShell
|
||||||
CommandShell string `yaml:"command_shell"`
|
|
||||||
AlertDown []string `yaml:"alert_down"`
|
AlertDown []string `yaml:"alert_down"`
|
||||||
AlertUp []string `yaml:"alert_up"`
|
AlertUp []string `yaml:"alert_up"`
|
||||||
CheckInterval float64 `yaml:"check_interval"`
|
CheckInterval float64 `yaml:"check_interval"`
|
||||||
@@ -30,10 +28,7 @@ type Monitor struct {
|
|||||||
// IsValid returns a boolean indicating if the Monitor has been correctly
|
// IsValid returns a boolean indicating if the Monitor has been correctly
|
||||||
// configured
|
// configured
|
||||||
func (monitor Monitor) IsValid() bool {
|
func (monitor Monitor) IsValid() bool {
|
||||||
atLeastOneCommand := (monitor.CommandShell != "" || monitor.Command != nil)
|
return (!monitor.Command.Empty() &&
|
||||||
atMostOneCommand := (monitor.CommandShell == "" || monitor.Command == nil)
|
|
||||||
return (atLeastOneCommand &&
|
|
||||||
atMostOneCommand &&
|
|
||||||
monitor.getAlertAfter() > 0 &&
|
monitor.getAlertAfter() > 0 &&
|
||||||
monitor.AlertDown != nil)
|
monitor.AlertDown != nil)
|
||||||
}
|
}
|
||||||
@@ -53,10 +48,10 @@ func (monitor Monitor) ShouldCheck() bool {
|
|||||||
// and a possible AlertNotice
|
// and a possible AlertNotice
|
||||||
func (monitor *Monitor) Check() (bool, *AlertNotice) {
|
func (monitor *Monitor) Check() (bool, *AlertNotice) {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if monitor.Command != nil {
|
if monitor.Command.Command != nil {
|
||||||
cmd = exec.Command(monitor.Command[0], monitor.Command[1:]...)
|
cmd = exec.Command(monitor.Command.Command[0], monitor.Command.Command[1:]...)
|
||||||
} else {
|
} else {
|
||||||
cmd = ShellCommand(monitor.CommandShell)
|
cmd = ShellCommand(monitor.Command.ShellCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -71,18 +66,20 @@ func (monitor *Monitor) Check() (bool, *AlertNotice) {
|
|||||||
alertNotice = monitor.failure()
|
alertNotice = monitor.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Command output: %s", monitor.lastOutput)
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Command output: %s", monitor.lastOutput)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Command result: %v", err)
|
if LogDebug {
|
||||||
|
log.Printf("DEBUG: Command result: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.Printf(
|
||||||
"monitor": monitor.Name,
|
"INFO: %s success=%t, alert=%t",
|
||||||
"success": isSuccess,
|
|
||||||
"alert": alertNotice != nil,
|
|
||||||
}).Infof(
|
|
||||||
"%s checked",
|
|
||||||
monitor.Name,
|
monitor.Name,
|
||||||
|
isSuccess,
|
||||||
|
alertNotice != nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
return isSuccess, alertNotice
|
return isSuccess, alertNotice
|
||||||
@@ -108,13 +105,15 @@ func (monitor *Monitor) failure() (notice *AlertNotice) {
|
|||||||
monitor.failureCount++
|
monitor.failureCount++
|
||||||
// If we haven't hit the minimum failures, we can exit
|
// If we haven't hit the minimum failures, we can exit
|
||||||
if monitor.failureCount < monitor.getAlertAfter() {
|
if monitor.failureCount < monitor.getAlertAfter() {
|
||||||
log.Debugf(
|
if LogDebug {
|
||||||
"%s failed but did not hit minimum failures. "+
|
log.Printf(
|
||||||
|
"DEBUG: %s failed but did not hit minimum failures. "+
|
||||||
"Count: %v alert after: %v",
|
"Count: %v alert after: %v",
|
||||||
monitor.Name,
|
monitor.Name,
|
||||||
monitor.failureCount,
|
monitor.failureCount,
|
||||||
monitor.getAlertAfter(),
|
monitor.getAlertAfter(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-13
@@ -13,16 +13,11 @@ func TestMonitorIsValid(t *testing.T) {
|
|||||||
expected bool
|
expected bool
|
||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, true, "Command only"},
|
{Monitor{Command: CommandOrShell{Command: []string{"echo", "test"}}, AlertDown: []string{"log"}}, true, "Command only"},
|
||||||
{Monitor{CommandShell: "echo test", AlertDown: []string{"log"}}, true, "CommandShell only"},
|
{Monitor{Command: CommandOrShell{ShellCommand: "echo test"}, AlertDown: []string{"log"}}, true, "CommandShell only"},
|
||||||
{Monitor{Command: []string{"echo", "test"}}, false, "No AlertDown"},
|
{Monitor{Command: CommandOrShell{Command: []string{"echo", "test"}}}, false, "No AlertDown"},
|
||||||
{Monitor{AlertDown: []string{"log"}}, false, "No commands"},
|
{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"}, CommandShell: "echo test", AlertDown: []string{"log"}},
|
|
||||||
false,
|
|
||||||
"Both commands",
|
|
||||||
},
|
|
||||||
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}, AlertAfter: -1}, false, "Invalid alert threshold, -1"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@@ -254,22 +249,22 @@ func TestMonitorCheck(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Monitor{Command: []string{"echo", "success"}},
|
Monitor{Command: CommandOrShell{Command: []string{"echo", "success"}}},
|
||||||
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
||||||
"Test successful command",
|
"Test successful command",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{CommandShell: "echo success"},
|
Monitor{Command: CommandOrShell{ShellCommand: "echo success"}},
|
||||||
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
|
||||||
"Test successful command shell",
|
"Test successful command shell",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{Command: []string{"total", "failure"}},
|
Monitor{Command: CommandOrShell{Command: []string{"total", "failure"}}},
|
||||||
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
||||||
"Test failed command",
|
"Test failed command",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Monitor{CommandShell: "false"},
|
Monitor{Command: CommandOrShell{ShellCommand: "false"}},
|
||||||
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
|
||||||
"Test failed command shell",
|
"Test failed command shell",
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ alerts:
|
|||||||
email_up:
|
email_up:
|
||||||
command: [sendmail, "me@minitor.mon", "Recovered: {monitor_name}", "We're back!"]
|
command: [sendmail, "me@minitor.mon", "Recovered: {monitor_name}", "We're back!"]
|
||||||
mailgun_down:
|
mailgun_down:
|
||||||
command_shell: >
|
command: >
|
||||||
curl -s -X POST
|
curl -s -X POST
|
||||||
-F subject="Alert! {{.MonitorName}} failed"
|
-F subject="Alert! {{.MonitorName}} failed"
|
||||||
-F from="Minitor <minitor@minitor.mon>"
|
-F from="Minitor <minitor@minitor.mon>"
|
||||||
@@ -34,7 +34,7 @@ alerts:
|
|||||||
https://api.mailgun.net/v3/minitor.mon/messages
|
https://api.mailgun.net/v3/minitor.mon/messages
|
||||||
-u "api:${MAILGUN_API_KEY}"
|
-u "api:${MAILGUN_API_KEY}"
|
||||||
sms_down:
|
sms_down:
|
||||||
command_shell: >
|
command: >
|
||||||
curl -s -X POST -F "Body=Failure! {{.MonitorName}} has failed"
|
curl -s -X POST -F "Body=Failure! {{.MonitorName}} has failed"
|
||||||
-F "From=${AVAILABLE_NUMBER}" -F "To=${MY_PHONE}"
|
-F "From=${AVAILABLE_NUMBER}" -F "To=${MY_PHONE}"
|
||||||
"https://api.twilio.com/2010-04-01/Accounts/${ACCOUNT_SID}/Messages"
|
"https://api.twilio.com/2010-04-01/Accounts/${ACCOUNT_SID}/Messages"
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
|
---
|
||||||
check_interval: 1
|
check_interval: 1
|
||||||
|
|
||||||
monitors:
|
monitors:
|
||||||
- name: Command
|
- name: Command
|
||||||
command: ['echo', '$PATH']
|
command: ['echo', '$PATH']
|
||||||
alert_down: [ 'log_command', 'log_shell' ]
|
alert_down: ['log_command', 'log_shell']
|
||||||
alert_every: 0
|
alert_every: 0
|
||||||
- name: Shell
|
- name: Shell
|
||||||
command_shell: >
|
command: >
|
||||||
echo 'Some string with stuff';
|
echo 'Some string with stuff';
|
||||||
echo 'another line';
|
echo 'another line';
|
||||||
echo $PATH;
|
echo $PATH;
|
||||||
exit 1
|
exit 1
|
||||||
alert_down: [ 'log_command', 'log_shell' ]
|
alert_down: ['log_command', 'log_shell']
|
||||||
alert_after: 5
|
alert_after: 5
|
||||||
alert_every: 0
|
alert_every: 0
|
||||||
|
|
||||||
alerts:
|
alerts:
|
||||||
log_command:
|
log_command:
|
||||||
command: [ 'echo', 'regular', '"command!!!"', "{{.MonitorName}}" ]
|
command: ['echo', 'regular', '"command!!!"', "{{.MonitorName}}"]
|
||||||
log_shell:
|
log_shell:
|
||||||
command_shell: echo "Failure on {{.MonitorName}} User is $USER"
|
command: echo "Failure on {{.MonitorName}} User is $USER"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
check_interval: 1
|
||||||
|
|
||||||
|
monitors:
|
||||||
|
- name: Command
|
||||||
|
command: ['echo', '$PATH']
|
||||||
|
alert_down: ['log']
|
||||||
|
alert_every: 0
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
check_interval: 1
|
||||||
|
|
||||||
|
monitors:
|
||||||
|
- name: Shell
|
||||||
|
command: >
|
||||||
|
echo 'Some string with stuff';
|
||||||
|
echo "<angle brackets>";
|
||||||
|
exit 1
|
||||||
|
alert_down: ['log_shell']
|
||||||
|
alert_after: 1
|
||||||
|
alert_every: 0
|
||||||
|
|
||||||
|
alerts:
|
||||||
|
log_shell:
|
||||||
|
command: |
|
||||||
|
echo 'Some string with stuff'
|
||||||
|
echo '<angle brackets>'
|
||||||
@@ -5,19 +5,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// escapeCommandShell accepts a command to be executed by a shell and escapes it
|
|
||||||
func escapeCommandShell(command string) string {
|
|
||||||
// Remove extra spaces and newlines from ends
|
|
||||||
command = strings.TrimSpace(command)
|
|
||||||
// TODO: Not sure if this part is actually needed. Should verify
|
|
||||||
// Escape double quotes since this will be passed in as an argument
|
|
||||||
command = strings.Replace(command, `"`, `\"`, -1)
|
|
||||||
return command
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShellCommand takes a string and executes it as a command using `sh`
|
// ShellCommand takes a string and executes it as a command using `sh`
|
||||||
func ShellCommand(command string) *exec.Cmd {
|
func ShellCommand(command string) *exec.Cmd {
|
||||||
shellCommand := []string{"sh", "-c", escapeCommandShell(command)}
|
shellCommand := []string{"sh", "-c", strings.TrimSpace(command)}
|
||||||
//log.Printf("Shell command: %v", shellCommand)
|
//log.Printf("Shell command: %v", shellCommand)
|
||||||
return exec.Command(shellCommand[0], shellCommand[1:]...)
|
return exec.Command(shellCommand[0], shellCommand[1:]...)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user