Compare commits
1 Commits
v0.1.0
..
prometheus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c7c0a470f |
+4
-105
@@ -1,114 +1,13 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: test
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:1.12
|
||||
commands:
|
||||
- make build
|
||||
|
||||
- name: test
|
||||
image: golang:1.12
|
||||
commands:
|
||||
- make build
|
||||
- make test
|
||||
|
||||
- name: check
|
||||
image: python:3
|
||||
commands:
|
||||
- pip install pre-commit==1.20.0
|
||||
- make check
|
||||
|
||||
- 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]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: publish
|
||||
|
||||
depends_on:
|
||||
- test
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
refs:
|
||||
- refs/heads/master
|
||||
- refs/tags/v*
|
||||
|
||||
steps:
|
||||
- name: build all binaries
|
||||
image: golang:1.12
|
||||
commands:
|
||||
- make all
|
||||
|
||||
- name: push image - arm
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: iamthefij/minitor-go
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
username:
|
||||
from_secret: docker_username
|
||||
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
+1
-3
@@ -16,6 +16,4 @@
|
||||
config.yml
|
||||
|
||||
# Output binary
|
||||
minitor
|
||||
minitor-linux-*
|
||||
minitor-darwin-amd64
|
||||
minitor-go
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-yaml
|
||||
args:
|
||||
- --allow-multiple-documents
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-merge-conflict
|
||||
- repo: git://github.com/dnephin/pre-commit-golang
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
- id: go-imports
|
||||
# - id: gometalinter
|
||||
# - id: golangci-lint
|
||||
+3
-23
@@ -1,28 +1,8 @@
|
||||
ARG REPO=library
|
||||
FROM multiarch/qemu-user-static:4.2.0-2 as qemu-user-static
|
||||
FROM ${REPO}/alpine:3.10
|
||||
FROM ${REPO}/busybox:latest
|
||||
WORKDIR /root/
|
||||
|
||||
# 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
|
||||
WORKDIR /app/
|
||||
|
||||
# Add common checking tools
|
||||
RUN apk --no-cache add bash=~5.0 curl=~7.66 jq=~1.6
|
||||
|
||||
# Add minitor user for running as non-root
|
||||
RUN addgroup -S minitor && adduser -S minitor -G minitor
|
||||
|
||||
# Copy scripts
|
||||
COPY ./scripts /app/scripts
|
||||
RUN chmod -R 755 /app/scripts
|
||||
|
||||
# Copy minitor in
|
||||
ARG ARCH=amd64
|
||||
COPY ./minitor-linux-${ARCH} ./minitor
|
||||
|
||||
# Drop to non-root user
|
||||
USER minitor
|
||||
COPY ./minitor-go ./minitor
|
||||
|
||||
ENTRYPOINT [ "./minitor" ]
|
||||
|
||||
+3
-21
@@ -1,7 +1,7 @@
|
||||
ARG REPO=library
|
||||
FROM golang:1.12-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git=~2
|
||||
RUN apk add --no-cache git
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
@@ -16,26 +16,8 @@ ARG VERSION=dev
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH}
|
||||
RUN go build -ldflags "-X main.version=${VERSION}" -a -installsuffix nocgo -o minitor .
|
||||
|
||||
FROM ${REPO}/alpine:3.10
|
||||
RUN mkdir /app
|
||||
WORKDIR /app/
|
||||
|
||||
# Copy minitor in
|
||||
FROM ${REPO}/busybox:latest
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /app/minitor .
|
||||
|
||||
# Add common checking tools
|
||||
RUN apk --no-cache add bash=~5.0 curl=~7.66 jq=~1.6
|
||||
|
||||
# Add minitor user for running as non-root
|
||||
RUN addgroup -S minitor && adduser -S minitor -G minitor
|
||||
|
||||
# Copy scripts
|
||||
COPY ./scripts /app/scripts
|
||||
RUN chmod -R 755 /app/scripts
|
||||
|
||||
# Drop to non-root user
|
||||
USER minitor
|
||||
|
||||
ENTRYPOINT [ "./minitor" ]
|
||||
|
||||
# vim: set filetype=dockerfile:
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
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: test
|
||||
default: test
|
||||
|
||||
.PHONY: build
|
||||
build: minitor
|
||||
build:
|
||||
go build
|
||||
|
||||
minitor:
|
||||
@echo Version: $(VERSION)
|
||||
go build -ldflags '-X "main.version=${VERSION}"' -o minitor
|
||||
minitor-go:
|
||||
go build
|
||||
|
||||
.PHONY: run
|
||||
run: minitor build
|
||||
./minitor -debug
|
||||
|
||||
.PHONY: run-metrics
|
||||
run-metrics: minitor build
|
||||
./minitor -debug -metrics
|
||||
run: minitor-go build
|
||||
./minitor-go -debug
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@@ -34,68 +24,16 @@ test:
|
||||
@go tool cover -func=coverage.out | awk -v target=80.0% \
|
||||
'/^total:/ { print "Total coverage: " $$3 " Minimum coverage: " target; if ($$3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }'
|
||||
|
||||
# Installs pre-commit hooks
|
||||
.PHONY: install-hooks
|
||||
install-hooks:
|
||||
pre-commit install --install-hooks
|
||||
|
||||
# Checks files for encryption
|
||||
.PHONY: check
|
||||
check:
|
||||
pre-commit run --all-files
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ./minitor
|
||||
rm -f ./minitor-linux-*
|
||||
rm -f ./minitor-darwin-amd64
|
||||
rm -f ./minitor-go
|
||||
rm -f ./coverage.out
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker build -f ./Dockerfile.multi-stage -t $(DOCKER_TAG)-linux-amd64 .
|
||||
docker build -f ./Dockerfile.multi-stage -t $(DOCKER_TAG) .
|
||||
|
||||
.PHONY: docker-run
|
||||
docker-run: docker-build
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
A reimplementation of [Minitor](https://git.iamthefij/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 very minimal monitoring tool. Python 3 was a quick way to get something live, but Python itself comes with a very large footprint.Thus Go feels like a better fit for the project, longer term.
|
||||
|
||||
Initial target is meant to be roughly compatible requiring only minor changes to configuration. Future iterations may diverge to take advantage of Go specific features.
|
||||
|
||||
@@ -30,7 +30,7 @@ monitors:
|
||||
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.
|
||||
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 containing Alert info and the built in Go templating format. Eg.
|
||||
|
||||
minitor-py:
|
||||
```yaml
|
||||
@@ -38,7 +38,7 @@ alerts:
|
||||
log_command:
|
||||
command: ['echo', '{monitor_name}']
|
||||
log_shell:
|
||||
command_shell: 'echo {monitor_name}'
|
||||
command_shell: "echo {monitor_name}"
|
||||
```
|
||||
|
||||
minitor-go:
|
||||
@@ -47,7 +47,7 @@ alerts:
|
||||
log_command:
|
||||
command: ['echo', '{{.MonitorName}}']
|
||||
log_shell:
|
||||
command_shell: 'echo {{.MonitorName}}'
|
||||
command_shell: "echo {{.MonitorName}}"
|
||||
```
|
||||
|
||||
Finally, newlines in a shell command don't terminate a particular command. Semicolons must be used and continuations should not.
|
||||
@@ -84,11 +84,10 @@ Pairity:
|
||||
- [x] Run alert commands
|
||||
- [x] Run alert commands in a shell
|
||||
- [x] Allow templating of alert commands
|
||||
- [x] Implement Prometheus client to export metrics
|
||||
- [x] Test coverage
|
||||
- [ ] Integration testing (manual or otherwise)
|
||||
- [ ] Implement Prometheus client to export metrics
|
||||
- [ ] Test coverage
|
||||
|
||||
Improvement (potentially breaking):
|
||||
Improvement:
|
||||
|
||||
- [ ] Implement leveled logging (maybe glog or logrus)
|
||||
- [ ] Consider switching from YAML to TOML
|
||||
@@ -96,4 +95,3 @@ Improvement (potentially breaking):
|
||||
- [ ] Consider dropping `alert_up` and `alert_down` in favor of using Go templates that offer more control of messaging
|
||||
- [ ] Async checking
|
||||
- [ ] Use durations rather than seconds checked in event loop
|
||||
- [ ] Revisit metrics and see if they all make sense
|
||||
|
||||
@@ -2,10 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Config type is contains all provided user configuration
|
||||
@@ -77,14 +77,14 @@ func LoadConfig(filePath string) (config Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
// TODO: Decide if this is better expanded here, or only when executing
|
||||
envExpanded := os.ExpandEnv(string(data))
|
||||
err = yaml.Unmarshal([]byte(envExpanded), &config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if LogDebug {
|
||||
log.Printf("DEBUG: Config values:\n%v\n", config)
|
||||
}
|
||||
log.Printf("config:\n%v\n", config)
|
||||
|
||||
if !config.IsValid() {
|
||||
err = errors.New("Invalid configuration")
|
||||
|
||||
+1
-66
@@ -23,74 +23,9 @@ func TestLoadConfig(t *testing.T) {
|
||||
_, err := LoadConfig(c.configPath)
|
||||
hasErr := (err != nil)
|
||||
if hasErr != c.expectErr {
|
||||
t.Errorf("LoadConfig(%v), expected_error=%v actual=%v", c.name, c.expectErr, err)
|
||||
t.Errorf("LoadConfig(%v), expected=%v actual=%v", c.name, "Err", err)
|
||||
log.Printf("Case failed: %s", c.name)
|
||||
}
|
||||
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].CommandShell
|
||||
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"].CommandShell
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -13,10 +15,6 @@ var (
|
||||
|
||||
// ExportMetrics will track whether or not we want to export metrics to prometheus
|
||||
ExportMetrics = false
|
||||
// MetricsPort is the port to expose metrics on
|
||||
MetricsPort = 8080
|
||||
// Metrics contains all active metrics
|
||||
Metrics = NewMetrics()
|
||||
|
||||
// version of minitor being run
|
||||
version = "dev"
|
||||
@@ -25,13 +23,7 @@ var (
|
||||
func checkMonitors(config *Config) error {
|
||||
for _, monitor := range config.Monitors {
|
||||
if monitor.ShouldCheck() {
|
||||
success, alertNotice := monitor.Check()
|
||||
|
||||
hasAlert := alertNotice != nil
|
||||
|
||||
// Track status metrics
|
||||
Metrics.SetMonitorStatus(monitor.Name, success)
|
||||
Metrics.CountCheck(monitor.Name, success, hasAlert)
|
||||
_, alertNotice := monitor.Check()
|
||||
|
||||
// Should probably consider refactoring everything below here
|
||||
if alertNotice != nil {
|
||||
@@ -63,9 +55,6 @@ func checkMonitors(config *Config) error {
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// Count alert metrics
|
||||
Metrics.CountAlert(monitor.Name, alert.Name)
|
||||
} else {
|
||||
// This case should never actually happen since we validate against it
|
||||
log.Printf("ERROR: Unknown alert for monitor %s: %s", alertNotice.MonitorName, alertName)
|
||||
@@ -79,6 +68,11 @@ func checkMonitors(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func serveMetrics() {
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
_ = http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Get debug flag
|
||||
flag.BoolVar(&LogDebug, "debug", false, "Enables debug logs (default: false)")
|
||||
@@ -101,7 +95,7 @@ func main() {
|
||||
// Serve metrics exporter, if specified
|
||||
if ExportMetrics {
|
||||
log.Println("INFO: Exporting metrics to Prometheus")
|
||||
go ServeMetrics()
|
||||
go serveMetrics()
|
||||
}
|
||||
|
||||
// Start main loop
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// TODO: Not sure if this is the best way to handle. A global instance for
|
||||
// metrics isn't bad, but it might be nice to curry versions of the metrics
|
||||
// for each monitor. Especially since every monitor has it's own. Perhaps
|
||||
// another new function that essentially curries each metric for a given
|
||||
// monitor name would do. This could be run when validating monitors and
|
||||
// initializing alert templates.
|
||||
|
||||
// MinitorMetrics contains all counters and metrics that Minitor will need to access
|
||||
type MinitorMetrics struct {
|
||||
alertCount *prometheus.CounterVec
|
||||
checkCount *prometheus.CounterVec
|
||||
monitorStatus *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// NewMetrics creates and initializes all metrics
|
||||
func NewMetrics() *MinitorMetrics {
|
||||
// Initialize all metrics
|
||||
metrics := &MinitorMetrics{
|
||||
alertCount: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "minitor_alert_total",
|
||||
Help: "Number of Minitor alerts",
|
||||
},
|
||||
[]string{"alert", "monitor"},
|
||||
),
|
||||
checkCount: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "minitor_check_total",
|
||||
Help: "Number of Minitor checks",
|
||||
},
|
||||
[]string{"monitor", "status", "is_alert"},
|
||||
),
|
||||
monitorStatus: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "minitor_monitor_up_count",
|
||||
Help: "Status of currently responsive monitors",
|
||||
},
|
||||
[]string{"monitor"},
|
||||
),
|
||||
}
|
||||
|
||||
// Register newly created metrics
|
||||
prometheus.MustRegister(metrics.alertCount)
|
||||
prometheus.MustRegister(metrics.checkCount)
|
||||
prometheus.MustRegister(metrics.monitorStatus)
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
// SetMonitorStatus sets the current status of Monitor
|
||||
func (metrics *MinitorMetrics) SetMonitorStatus(monitor string, isUp bool) {
|
||||
val := 0.0
|
||||
if isUp {
|
||||
val = 1.0
|
||||
}
|
||||
metrics.monitorStatus.With(prometheus.Labels{"monitor": monitor}).Set(val)
|
||||
}
|
||||
|
||||
// CountCheck counts the result of a particular Monitor check
|
||||
func (metrics *MinitorMetrics) CountCheck(monitor string, isSuccess bool, isAlert bool) {
|
||||
status := "failure"
|
||||
if isSuccess {
|
||||
status = "success"
|
||||
}
|
||||
|
||||
alertVal := "false"
|
||||
if isAlert {
|
||||
alertVal = "true"
|
||||
}
|
||||
|
||||
metrics.checkCount.With(
|
||||
prometheus.Labels{"monitor": monitor, "status": status, "is_alert": alertVal},
|
||||
).Inc()
|
||||
}
|
||||
|
||||
// CountAlert counts an alert
|
||||
func (metrics *MinitorMetrics) CountAlert(monitor string, alert string) {
|
||||
metrics.alertCount.With(
|
||||
prometheus.Labels{
|
||||
"alert": alert,
|
||||
"monitor": monitor,
|
||||
},
|
||||
).Inc()
|
||||
}
|
||||
|
||||
// ServeMetrics starts an http server with a Prometheus metrics handler
|
||||
func ServeMetrics() {
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
host := fmt.Sprintf(":%d", MetricsPort)
|
||||
_ = http.ListenAndServe(host, nil)
|
||||
}
|
||||
+4
-2
@@ -155,16 +155,18 @@ func (monitor Monitor) getAlertAfter() int16 {
|
||||
// Zero is one!
|
||||
if monitor.AlertAfter == 0 {
|
||||
return 1
|
||||
} else {
|
||||
return monitor.AlertAfter
|
||||
}
|
||||
return monitor.AlertAfter
|
||||
}
|
||||
|
||||
// GetAlertNames gives a list of alert names for a given monitor status
|
||||
func (monitor Monitor) GetAlertNames(up bool) []string {
|
||||
if up {
|
||||
return monitor.AlertUp
|
||||
} else {
|
||||
return monitor.AlertDown
|
||||
}
|
||||
return monitor.AlertDown
|
||||
}
|
||||
|
||||
func (monitor Monitor) createAlertNotice(isUp bool) *AlertNotice {
|
||||
|
||||
+12
-24
@@ -1,41 +1,29 @@
|
||||
---
|
||||
check_interval: 5
|
||||
check_interval: 30
|
||||
|
||||
monitors:
|
||||
- name: 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: 10 # Must be at minimum the global `check_interval`
|
||||
- name: My Website
|
||||
command: [ 'curl', '-s', '-o', '/dev/null', 'https://minitor.mon' ]
|
||||
alert_down: [ log, mailgun_down, sms_down ]
|
||||
alert_up: [ log, email_up ]
|
||||
check_interval: 30 # Must be at minimum the global `check_interval`
|
||||
alert_after: 3
|
||||
alert_every: -1 # Defaults to -1 for exponential backoff. 0 to disable repeating
|
||||
- name: 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: 5
|
||||
alert_after: 3
|
||||
alert_every: -1
|
||||
alert_every: -1 # Defaults to -1 for exponential backoff. 0 to disable repeating
|
||||
|
||||
alerts:
|
||||
log_down:
|
||||
command: ["echo", "Minitor failure for {{.MonitorName}}"]
|
||||
log_up:
|
||||
command: ["echo", "Minitor recovery for {{.MonitorName}}"]
|
||||
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:
|
||||
command_shell: >
|
||||
command: >
|
||||
curl -s -X POST
|
||||
-F subject="Alert! {{.MonitorName}} failed"
|
||||
-F subject="Alert! {monitor_name} 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}"
|
||||
sms_down:
|
||||
command_shell: >
|
||||
curl -s -X POST -F "Body=Failure! {{.MonitorName}} has failed"
|
||||
command: >
|
||||
curl -s -X POST -F "Body=Failure! {monitor_name} 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}"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Minitor Scripts
|
||||
|
||||
A collection of some handy scripts to use with Minitor
|
||||
|
||||
These are not included with the Python package, but they are included in the Docker image in `/app/scripts`.
|
||||
@@ -1,51 +0,0 @@
|
||||
#! /bin/bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# docker_check.sh
|
||||
#
|
||||
# Checks the most recent state exit code of a Docker container
|
||||
#################
|
||||
|
||||
# Docker host will default to a socket
|
||||
# To override, export DOCKER_HOST to a new hostname
|
||||
DOCKER_HOST="${DOCKER_HOST:=socket}"
|
||||
container_name="$1"
|
||||
|
||||
# Curls Docker either using a socket or URL
|
||||
function curl_docker {
|
||||
local path="$1"
|
||||
if [ "$DOCKER_HOST" == "socket" ]; then
|
||||
curl --unix-socket /var/run/docker.sock "http://localhost/$path" 2>/dev/null
|
||||
else
|
||||
curl "http://${DOCKER_HOST}/$path" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Returns caintainer ID for a given container name
|
||||
function get_container_id {
|
||||
local container_name="$1"
|
||||
curl_docker 'containers/json?all=1' \
|
||||
| jq -r ".[] | {Id, Name: .Names[]} | select(.Name == \"/${container_name}\") | .Id"
|
||||
}
|
||||
|
||||
# Returns container JSON
|
||||
function inspect_container {
|
||||
local container_id=$1
|
||||
curl_docker "containers/$container_id/json"
|
||||
}
|
||||
|
||||
if [ -z "$container_name" ]; then
|
||||
echo "Usage: $0 container_name"
|
||||
echo "Will exit with the last status code of continer with provided name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
container_id=$(get_container_id $container_name)
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "ERROR: Could not find container with name: $container_name"
|
||||
exit 1
|
||||
fi
|
||||
exit_code=$(inspect_container "$container_id" | jq -r .State.ExitCode)
|
||||
|
||||
exit "$exit_code"
|
||||
@@ -1,61 +0,0 @@
|
||||
#! /bin/bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# docker_healthcheck.sh
|
||||
#
|
||||
# Returns the results of a Docker Healthcheck for a container
|
||||
#################
|
||||
|
||||
# Docker host will default to a socket
|
||||
# To override, export DOCKER_HOST to a new hostname
|
||||
DOCKER_HOST="${DOCKER_HOST:=socket}"
|
||||
container_name="$1"
|
||||
|
||||
# Curls Docker either using a socket or URL
|
||||
function curl_docker {
|
||||
local path="$1"
|
||||
if [ "$DOCKER_HOST" == "socket" ]; then
|
||||
curl --unix-socket /var/run/docker.sock "http://localhost/$path" 2>/dev/null
|
||||
else
|
||||
curl "http://${DOCKER_HOST}/$path" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Returns caintainer ID for a given container name
|
||||
function get_container_id {
|
||||
local container_name="$1"
|
||||
curl_docker 'containers/json?all=1' \
|
||||
| jq -r ".[] | {Id, Name: .Names[]} | select(.Name == \"/${container_name}\") | .Id"
|
||||
}
|
||||
|
||||
# Returns container JSON
|
||||
function inspect_container {
|
||||
local container_id="$1"
|
||||
curl_docker "containers/$container_id/json"
|
||||
}
|
||||
|
||||
if [ -z "$container_name" ]; then
|
||||
echo "Usage: $0 container_name"
|
||||
echo "Will return results of healthcheck for continer with provided name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
container_id=$(get_container_id "$container_name")
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "ERROR: Could not find container with name: $container_name"
|
||||
exit 1
|
||||
fi
|
||||
health=$(inspect_container "$container_id" | jq -r '.State.Health.Status')
|
||||
|
||||
case "$health" in
|
||||
null)
|
||||
echo "No healthcheck results"
|
||||
;;
|
||||
starting|healthy)
|
||||
echo "Status: '$health'"
|
||||
;;
|
||||
*)
|
||||
echo "Status: '$health'"
|
||||
exit 1
|
||||
esac
|
||||
@@ -6,3 +6,4 @@ monitors:
|
||||
alert_down: [ 'alert_down', 'log_shell', 'log_command' ]
|
||||
# alert_every: -1
|
||||
alert_every: 0
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
check_interval: 1
|
||||
|
||||
monitors:
|
||||
- name: Command
|
||||
command: ['echo', '$PATH']
|
||||
alert_down: ['log_command', 'log_shell']
|
||||
alert_down: [ 'log_command', 'log_shell' ]
|
||||
alert_every: 0
|
||||
- name: Shell
|
||||
command_shell: >
|
||||
@@ -12,12 +11,12 @@ monitors:
|
||||
echo 'another line';
|
||||
echo $PATH;
|
||||
exit 1
|
||||
alert_down: ['log_command', 'log_shell']
|
||||
alert_down: [ 'log_command', 'log_shell' ]
|
||||
alert_after: 5
|
||||
alert_every: 0
|
||||
|
||||
alerts:
|
||||
log_command:
|
||||
command: ['echo', 'regular', '"command!!!"', "{{.MonitorName}}"]
|
||||
command: [ 'echo', 'regular', '"command!!!"', "{{.MonitorName}}" ]
|
||||
log_shell:
|
||||
command_shell: echo "Failure on {{.MonitorName}} User is $USER"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
check_interval: 1
|
||||
|
||||
monitors:
|
||||
- name: Shell
|
||||
command_shell: >
|
||||
echo 'Some string with stuff';
|
||||
echo "<angle brackets>";
|
||||
exit 1
|
||||
alert_down: ['log_shell']
|
||||
alert_after: 1
|
||||
alert_every: 0
|
||||
|
||||
alerts:
|
||||
log_shell:
|
||||
command_shell: |
|
||||
echo 'Some string with stuff'
|
||||
echo '<angle brackets>'
|
||||
@@ -5,9 +5,19 @@ import (
|
||||
"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`
|
||||
func ShellCommand(command string) *exec.Cmd {
|
||||
shellCommand := []string{"sh", "-c", strings.TrimSpace(command)}
|
||||
shellCommand := []string{"sh", "-c", escapeCommandShell(command)}
|
||||
//log.Printf("Shell command: %v", shellCommand)
|
||||
return exec.Command(shellCommand[0], shellCommand[1:]...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user