diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6797f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.DS_Store +sentry-gateway +dist/ +vendor/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0655f92 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.4 +MAINTAINER Moto Ishizawa "summerwind.jp" + +COPY ./sentry-gateway /bin/sentry-gateway + +ENTRYPOINT ["sentry-gateway"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..378113f --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2017 Moto Ishizawa + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..962124d --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +NAME=alertmanager-sentry-gateway +VERSION=0.1.0 +COMMIT=$(shell git rev-parse --verify HEAD) + +PACKAGES=$(shell go list ./...) +BUILD_FLAGS=-ldflags "-X main.VERSION=$(VERSION) -X main.COMMIT=$(COMMIT)" + +.PHONY: build test container clean + +build: vendor + go build $(BUILD_FLAGS) sentry-gateway.go + +test: vendor + go test -v $(PACKAGES) + go vet $(PACKAGES) + +container: + GOARCH=amd64 GOOS=linux go build $(BUILD_FLAGS) sentry-gateway.go + docker build -t summerwind/$(NAME):latest -t summerwind/$(NAME):$(VERSION) . + rm -rf sentry-gateway + +clean: + rm -rf sentry-gateway + rm -rf dist + +dist: + mkdir -p dist + + GOARCH=amd64 GOOS=darwin go build $(BUILD_FLAGS) sentry-gateway.go + tar -czf dist/${NAME}_darwin_amd64.tar.gz sentry-gateway + rm -rf sentry-gateway + + GOARCH=amd64 GOOS=linux go build $(BUILD_FLAGS) sentry-gateway.go + tar -czf dist/${NAME}_linux_amd64.tar.gz sentry-gateway + rm -rf sentry-gateway + + GOARCH=arm64 GOOS=linux go build $(BUILD_FLAGS) sentry-gateway.go + tar -czf dist/${NAME}_linux_arm64.tar.gz sentry-gateway + rm -rf sentry-gateway + + GOARCH=arm GOOS=linux go build $(BUILD_FLAGS) sentry-gateway.go + tar -czf dist/${NAME}_linux_arm.tar.gz sentry-gateway + rm -rf sentry-gateway + +vendor: + glide install diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fb5316 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# alertmanager-sentry-gateway + +alertmanager-sentry-gateway is a webhook gateway for [Alertmanager](https://github.com/prometheus/alertmanager). This gateway recives webhooks from Alertmanager and sends alert information as an event to [Sentry](https://sentry.io). + +## Install + +### Just want the binary? + +Go to the [releases page](https://github.com/summerwind/alertmanager-sentry-gateway/releases), find the version you want, and download the tarball file. + +### Run as container? + +``` +$ docker pull summerwind/alertmanager-sentry-gateway:latest +``` + +### Building binary yourself + +To build the binary you need to install [Go](https://golang.org/) and [Glide](https://github.com/Masterminds/glide). + +``` +$ make +``` + +## Usage + +Sentry's DSN is required to run this gateway. + +``` +$ sentry-gateway --dsn ${SENTRY_DSN} +``` + +Event body of Sentry can be customized with a template file as follows. The data passed to the template file is an [Alert](https://godoc.org/github.com/prometheus/alertmanager/template#Alert) of Alertmanager. + +``` +$ vim template.tmpl +``` +``` +{{ .Labels.alertname }} - {{ .Labels.instance }} +{{ .Annotations.description }} + +Labels: +{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} +{{ end }} +``` +``` +$ sentry-gateway --dsn ${SENTRY_DSN} --template template.tmpl +``` + +## Alertmanager Configuration + +To enable Alertmanager to send alerts to this gateway you need to configure a webhook in Alertmanager. + +``` +receivers: +- name: team + webhook_configs: + - url: 'http://127.0.0.1:9096/sentry' + send_resolved: false +``` diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..7637f66 --- /dev/null +++ b/glide.lock @@ -0,0 +1,105 @@ +hash: 9d7b6963f31b9f9610d4c2c50038aa06d4d9e3728e7d0db90c709bc6d4e0b909 +updated: 2017-11-10T12:18:06.117299163+09:00 +imports: +- name: github.com/beorn7/perks + version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 + subpackages: + - quantile +- name: github.com/cenkalti/backoff + version: 309aa717adbf351e92864cbedf9cca0b769a4b5a +- name: github.com/certifi/gocertifi + version: a4ab0227d360091084658029ec8ae45f989fba3e +- name: github.com/cespare/xxhash + version: 5c37fe3735342a2e0d01c87a907579987c8936cc +- name: github.com/getsentry/raven-go + version: d3909e6151a9d4ce1baed14ceb3ad7a835f7c91f +- name: github.com/go-kit/kit + version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c + subpackages: + - log + - log/level +- name: github.com/go-logfmt/logfmt + version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-stack/stack + version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf +- name: github.com/gogo/protobuf + version: 616a82ed12d78d24d4839363e8f3c5d3f20627cf + subpackages: + - proto + - sortkeys + - types +- name: github.com/golang/protobuf + version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 + subpackages: + - proto +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/kr/logfmt + version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 +- name: github.com/matttproud/golang_protobuf_extensions + version: c12348ce28de40eed0136aa2b644d0ee0650e56c + subpackages: + - pbutil +- name: github.com/oklog/oklog + version: b227e3c962b76627815ad45e53a5c6f7eef672f1 + subpackages: + - pkg/group +- name: github.com/pkg/errors + version: c605e284fe17294bda444b34710735b29d1a9d90 +- name: github.com/prometheus/alertmanager + version: 133c888ef3644b47a52acbaeffb09f4cc637df1b + subpackages: + - config + - inhibit + - nflog + - nflog/nflogpb + - notify + - provider + - silence + - silence/silencepb + - template + - template/internal/deftmpl + - types +- name: github.com/prometheus/client_golang + version: 5cec1d0429b02e4323e042eb04dafdb079ddf568 + subpackages: + - prometheus +- name: github.com/prometheus/client_model + version: 6f3806018612930941127f2a7c6c453ba2c527d2 + subpackages: + - go +- name: github.com/prometheus/common + version: e3fb1a1acd7605367a2b378bc2e2f893c05174b7 + subpackages: + - expfmt + - internal/bitbucket.org/ww/goautoneg + - model + - version +- name: github.com/prometheus/procfs + version: a6e9df898b1336106c743392c48ee0b71f5c4efa + subpackages: + - xfs +- name: github.com/satori/go.uuid + version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b +- name: github.com/spf13/cobra + version: 7be4beda01ec05d0b93d80b3facd2b6f44080d94 +- name: github.com/spf13/pflag + version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 +- name: github.com/weaveworks/mesh + version: 38e8100dfbc7b28ae8ffde5a2bbeceec57ffa52a +- name: golang.org/x/crypto + version: 7e9105388ebff089b3f99f0ef676ea55a6da3a7e + subpackages: + - curve25519 + - nacl/box + - nacl/secretbox + - poly1305 + - salsa20/salsa +- name: golang.org/x/net + version: f2499483f923065a842d38eb4c7f1927e6fc6e6d + subpackages: + - context + - context/ctxhttp +- name: gopkg.in/yaml.v2 + version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..53f696f --- /dev/null +++ b/glide.yaml @@ -0,0 +1,8 @@ +package: github.com/summerwind/alertmanager-sentry-gateway +import: + - package: github.com/spf13/cobra + version: 7be4beda01ec05d0b93d80b3facd2b6f44080d94 + - package: github.com/getsentry/raven-go + version: d3909e6151a9d4ce1baed14ceb3ad7a835f7c91f + - package: github.com/prometheus/alertmanager + version: 133c888ef3644b47a52acbaeffb09f4cc637df1b diff --git a/sentry-gateway.go b/sentry-gateway.go new file mode 100644 index 0000000..40fc456 --- /dev/null +++ b/sentry-gateway.go @@ -0,0 +1,184 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "html/template" + "io/ioutil" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/getsentry/raven-go" + "github.com/prometheus/alertmanager/notify" + amt "github.com/prometheus/alertmanager/template" + "github.com/spf13/cobra" +) + +var ( + VERSION string = "latest" + COMMIT string = "HEAD" +) + +const ( + defaultTemplate = "{{ .Labels.alertname }} - {{ .Labels.instance }}\n{{ .Annotations.description }}" +) + +func main() { + var cmd = &cobra.Command{ + Use: "sentry-gateway", + Short: "Sentry gateway for Alertmanager", + RunE: run, + } + + cmd.Flags().StringP("dsn", "d", "", "Sentry DSN") + cmd.Flags().StringP("template", "t", "", "Path of the template file of event message") + cmd.Flags().StringP("addr", "a", "0.0.0.0:9096", "Address to listen on for WebHook") + cmd.Flags().Bool("version", false, "Display version information and exit") + + cmd.SilenceUsage = true + cmd.SilenceErrors = true + + err := cmd.Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } +} + +func run(cmd *cobra.Command, args []string) error { + v, err := cmd.Flags().GetBool("version") + if err != nil { + return err + } + + if v { + version() + os.Exit(0) + } + + dsn, err := cmd.Flags().GetString("dsn") + if err != nil { + return err + } + + tmplPath, err := cmd.Flags().GetString("template") + if err != nil { + return err + } + + addr, err := cmd.Flags().GetString("addr") + if err != nil { + return err + } + + if dsn == "" { + return errors.New("Sentry DSN required") + } + raven.SetDSN(dsn) + + tmpl := defaultTemplate + if tmplPath != "" { + file, err := ioutil.ReadFile(tmplPath) + if err != nil { + return err + } + + tmpl = string(file) + } + + t := template.New("").Option("missingkey=zero") + t.Funcs(template.FuncMap(amt.DefaultFuncs)) + t, err = t.Parse(tmpl) + if err != nil { + return err + } + + hookChan := make(chan *notify.WebhookMessage) + + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var wh notify.WebhookMessage + + decoder := json.NewDecoder(r.Body) + defer r.Body.Close() + + err := decoder.Decode(&wh) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid webhook: %s\n", err) + return + } + + hookChan <- &wh + }) + + s := &http.Server{ + Addr: addr, + Handler: mux, + } + + go func() { + err := s.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + fmt.Fprintf(os.Stderr, "Unable to start server: %s\n", err) + os.Exit(1) + } + }() + + go worker(hookChan, t) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) + <-sigCh + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + err = s.Shutdown(ctx) + if err != nil { + return err + } + + for len(hookChan) > 0 { + time.Sleep(1) + } + close(hookChan) + + return nil +} + +func worker(hookChan chan *notify.WebhookMessage, t *template.Template) { + for wh := range hookChan { + for _, alert := range wh.Alerts { + var buf bytes.Buffer + + err := t.Execute(&buf, alert) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid template: %s\n", err) + continue + } + + packet := &raven.Packet{ + Timestamp: raven.Timestamp(alert.StartsAt), + Message: buf.String(), + Extra: map[string]interface{}{}, + Logger: "alertmanager", + } + + eventID, ch := raven.Capture(packet, alert.Labels) + <-ch + + log.Printf("event_id:%s alert_name:%s\n", eventID, alert.Labels["alertname"]) + } + } +} + +func version() { + fmt.Printf("Version: %s (%s)\n", VERSION, COMMIT) +}