From 64904484393721e59f094d9aa35dc47e70659b6c Mon Sep 17 00:00:00 2001 From: Paul Lecuq Date: Sun, 31 Mar 2019 17:00:38 +0200 Subject: [PATCH] initial commit, based on sensu-email-handler --- CHANGELOG.md | 11 +++ Gopkg.lock | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 38 +++++++++ LICENSE | 43 ++++++++++ README.md | 64 ++++++++++++++ event.json | 121 ++++++++++++++++++++++++++ main.go | 132 +++++++++++++++++++++++++++++ 7 files changed, 642 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 event.json create mode 100644 main.go diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..560b837 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic +Versioning](http://semver.org/spec/v2.0.0.html). + +## [0.0.1] - 2019-03-31 + +### Added +- Initial release, based on sensu-email-handler diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..310b822 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,233 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:660af934d331e79d29317ffd5c76fceedaa1249167b1ee461d284c58d53291c6" + name = "github.com/coreos/etcd" + packages = [ + "auth/authpb", + "etcdserver/etcdserverpb", + "mvcc/mvccpb", + ] + pruneopts = "UT" + revision = "27fc7e2296f506182f58ce846e48f36b34fe6842" + version = "v3.3.10" + +[[projects]] + digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + pruneopts = "UT" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + +[[projects]] + digest = "1:9d5bd7c02a73959247ccbf524180e363631ece23c58e12b21d697f4741c7926f" + name = "github.com/echlebek/timeproxy" + packages = ["."] + pruneopts = "UT" + revision = "d7c2918981322d4d437f7a52f3a15c7ec5a4f488" + version = "v1.0.0" + +[[projects]] + digest = "1:bfc758d5a03d57d97226fac6934551c01bd76612adb119c177395b057a0a46db" + name = "github.com/gogo/protobuf" + packages = [ + "gogoproto", + "proto", + "protoc-gen-gogo/descriptor", + ] + pruneopts = "UT" + revision = "636bf0302bc95575d69441b25a2603156ffdddf1" + version = "v1.1.1" + +[[projects]] + digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf" + name = "github.com/golang/protobuf" + packages = [ + "proto", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/timestamp", + ] + pruneopts = "UT" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" + +[[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + pruneopts = "UT" + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + digest = "1:1ed5ad32e321bd35d51e30dbbccab9d226ac290347838d03362b427459036a91" + name = "github.com/json-iterator/go" + packages = ["."] + pruneopts = "UT" + revision = "f7279a603edee96fe7764d3de9c6ff8cf9970994" + +[[projects]] + branch = "master" + digest = "1:dbfe572cc258e5bcf54cb650a06d90edd0da04e42ca1ed909cc1d49f00011c63" + name = "github.com/robertkrimen/otto" + packages = [ + ".", + "ast", + "dbg", + "file", + "parser", + "registry", + "token", + ] + pruneopts = "UT" + revision = "15f95af6e78dcd2030d8195a138bd88d4f403546" + +[[projects]] + digest = "1:bffc9efe1d7783f54cb0003b57cb3c8fe49f56beb9859cf122be97504e854666" + name = "github.com/robfig/cron" + packages = ["."] + pruneopts = "UT" + revision = "2315d5715e36303a941d907f038da7f7c44c773b" + +[[projects]] + digest = "1:b9cc662e15cbe325399cb8a8610962a4b8373512c2f3318d0b30e856ca081c64" + name = "github.com/sensu/sensu-go" + packages = [ + "api/core/v2", + "js", + "types", + "util/strings", + ] + pruneopts = "UT" + revision = "0150bbcb0a2b70f720fa20a9a47c7e91bacd72b1" + +[[projects]] + digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939" + name = "github.com/spf13/cobra" + packages = ["."] + pruneopts = "UT" + revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" + version = "v0.0.3" + +[[projects]] + digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" + name = "github.com/spf13/pflag" + packages = ["."] + pruneopts = "UT" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" + +[[projects]] + branch = "master" + digest = "1:6ca51c5d8a610b3da56856df7a8f8f3e075eba8d5f7a4acbadd79b2d2a368054" + name = "golang.org/x/net" + packages = [ + "context", + "http/httpguts", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "trace", + ] + pruneopts = "UT" + revision = "adae6a3d119ae4890b46832a2e88a95adc62b8e7" + +[[projects]] + branch = "master" + digest = "1:a62e80faf19c93dcbd7954a26fdb51acbfcb4b7161966adf1de531e593c4834a" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "93218def8b18e66adbdab3eca8ec334700329f1f" + +[[projects]] + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + ] + pruneopts = "UT" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + branch = "master" + digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" + name = "google.golang.org/genproto" + packages = ["googleapis/rpc/status"] + pruneopts = "UT" + revision = "b5d43981345bdb2c233eb4bf3277847b48c6fdc6" + +[[projects]] + digest = "1:c3ad9841823db6da420a5625b367913b4ff54bbe60e8e3c98bd20e243e62e2d2" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclog", + "internal", + "internal/backoff", + "internal/channelz", + "internal/envconfig", + "internal/grpcrand", + "internal/transport", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + ] + pruneopts = "UT" + revision = "2e463a05d100327ca47ac218281906921038fd95" + version = "v1.16.0" + +[[projects]] + digest = "1:9935525a8c49b8434a0b0a54e1980e94a6fae73aaff45c5d33ba8dff69de123e" + name = "gopkg.in/sourcemap.v1" + packages = [ + ".", + "base64vlq", + ] + pruneopts = "UT" + revision = "6e83acea0053641eff084973fee085f0c193c61a" + version = "v1.0.5" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/sensu/sensu-go/types", + "github.com/spf13/cobra", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..6f8c4bd --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,38 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + name = "github.com/spf13/cobra" + version = "0.0.3" + +[[constraint]] + name = "github.com/sensu/sensu-go" + revision = "0150bbcb0a2b70f720fa20a9a47c7e91bacd72b1" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81cad9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,43 @@ +MIT License + +Copyright (c) 2019 PaulBSD + +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. + +MIT License + +Copyright (c) 2019 Sensu Inc. + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab2e1b3 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Sensu Go Telegram Handler Plugin + +The Sensu Go Telegram Handler is a [Sensu Event Handler][2] for sending +incident notification emails. + +## Installation + +Download the latest version of the sensu-telegram-handler from [releases][1], +or create an executable script from this source. + +From the local path of the sensu-telegram-handler repository: + +``` +go build -o /usr/local/bin/sensu-telegram-handler main.go +``` + +## Configuration + +Example Sensu Go definition: + +```json +{ + "api_version": "core/v2", + "type": "Handler", + "metadata": { + "namespace": "default", + "name": "telegram" + }, + "spec": { + "type": "pipe", + "command": "sensu-telegram-handler -t 123456789:dahxaemuGoM8ekeed1pideeCoh3ahm-abcd -c 9876543210", + "timeout": 10, + "filters": [ + "is_incident", + "not_silenced" + ] + } +} +``` + +## Usage Examples + +Help: + +``` +The Sensu Go Telegram handler for sending an email notification + +Usage: + sensu-telegram-handler [flags] + +Flags: + -T, --bodyTemplateFile string A template file to use for the body + -c, --channel int The channel (default -1) + -h, --help help for sensu-telegram-handler + -H, --hookout Include output from check hook(s) + -t, --token string the telegram bot token +``` + +## Contributing + +See https://github.com/sensu/sensu-go/blob/master/CONTRIBUTING.md + +[1]: https://github.com/sensu/sensu-email-handler/releases (code base) +[2]: https://docs.sensu.io/sensu-go/5.0/reference/handlers/#how-do-sensu-handlers-work diff --git a/event.json b/event.json new file mode 100644 index 0000000..f9216a3 --- /dev/null +++ b/event.json @@ -0,0 +1,121 @@ +{ + "entity": { + "entity_class": "agent", + "system": { + "hostname": "webserver01", + "os": "linux", + "platform": "centos", + "platform_family": "rhel", + "platform_version": "7.4.1708", + "network": { + "interfaces": [ + { + "name": "lo", + "addresses": [ + "127.0.0.1/8", + "::1/128" + ] + }, + { + "name": "enp0s3", + "mac": "08:00:27:11:ad:d2", + "addresses": [ + "10.0.2.15/24", + "fe80::26a5:54ec:cf0d:9704/64" + ] + }, + { + "name": "enp0s8", + "mac": "08:00:27:bc:be:60", + "addresses": [ + "172.28.128.3/24", + "fe80::a00:27ff:febc:be60/64" + ] + } + ] + }, + "arch": "amd64" + }, + "subscriptions": [ + "testing", + "entity:webserver01" + ], + "last_seen": 1542667635, + "deregister": false, + "deregistration": {}, + "user": "agent", + "redact": [ + "password", + "passwd", + "pass", + "api_key", + "api_token", + "access_key", + "secret_key", + "private_key", + "secret" + ], + "metadata": { + "name": "webserver01", + "namespace": "default", + "labels": null, + "annotations": null + } + }, + "check": { + "check_hooks": null, + "duration": 1.903135228, + "executed": 1522100915, + "high_flap_threshold": 0, + "history": [ + { + "executed": 1522100315 + }, + { + "executed": 1522100915 + } + ], + "command": "http_check.sh http://localhost:80", + "handlers": [ + "slack" + ], + "high_flap_threshold": 0, + "interval": 20, + "low_flap_threshold": 0, + "publish": true, + "runtime_assets": [], + "subscriptions": [ + "testing" + ], + "proxy_entity_name": "", + "check_hooks": null, + "stdin": false, + "ttl": 0, + "timeout": 0, + "duration": 0.010849143, + "executed": 1542667666, + "history": [ + { + "status": 1, + "executed": 1542667666 + } + ], + "issued": 1542667666, + "output": "example output", + "state": "failing", + "status": 1, + "total_state_change": 0, + "last_ok": 0, + "occurrences": 1, + "occurrences_watermark": 1, + "output_metric_format": "", + "output_metric_handlers": [], + "env_vars": null, + "metadata": { + "name": "check-nginx", + "namespace": "default", + "labels": null, + "annotations": null + } + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6f73580 --- /dev/null +++ b/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "text/template" + + "github.com/sensu/sensu-go/types" + "github.com/spf13/cobra" + "github.com/go-telegram-bot-api/telegram-bot-api" +) + +var ( + token string + channel int64 + subject string + hookout bool + bodyTemplateFile string + stdin *os.File + + emailSubjectTemplate = "Sensu Alert - {{.Entity.Name}}/{{.Check.Name}}: {{.Check.State}}" + emailBodyTemplate = "{{.Check.Output}}" +) + +func main() { + cmd := &cobra.Command{ + Use: "sensu-telegram-handler", + Short: "The Sensu Go Telegram handler for sending an email notification", + RunE: run, + } + + cmd.Flags().StringVarP(&token, "token", "t", "", "the telegram bot token") + cmd.Flags().Int64VarP(&channel, "channel", "c", -1, "The channel") + cmd.Flags().BoolVarP(&hookout, "hookout", "H", false, "Include output from check hook(s)") + cmd.Flags().StringVarP(&bodyTemplateFile, "bodyTemplateFile", "T", "", "A template file to use for the body") + + cmd.Execute() +} + +func run(cmd *cobra.Command, args []string) error { + validationError := checkArgs() + if validationError != nil { + return validationError + } + + if stdin == nil { + stdin = os.Stdin + } + + eventJSON, err := ioutil.ReadAll(stdin) + if err != nil { + return fmt.Errorf("failed to read stdin: %s", err) + } + + event := &types.Event{} + err = json.Unmarshal(eventJSON, event) + if err != nil { + return fmt.Errorf("failed to unmarshal stdin data: %s", err) + } + + if err = event.Validate(); err != nil { + return fmt.Errorf("failed to validate event: %s", err) + } + + if !event.HasCheck() { + return fmt.Errorf("event does not contain check") + } + + sendMsg(event) + + return nil +} + +func checkArgs() error { + if hookout && len(bodyTemplateFile) > 0 { + return errors.New("--hookout (-H) and --bodyTemplateFile (-T) are mutually exclusive") + } + if hookout { + emailBodyTemplate = "{{.Check.Output}}\n{{range .Check.Hooks}}Hook Name: {{.Name}}\nHook Command: {{.Command}}\n\n{{.Output}}\n\n{{end}}" + } else if len(bodyTemplateFile) > 0 { + templateBytes, fileErr := ioutil.ReadFile(bodyTemplateFile) + if fileErr != nil { + return fmt.Errorf("failed to read specified template file %s", bodyTemplateFile) + } + emailBodyTemplate = string(templateBytes) + } + return nil +} + +func sendMsg(event *types.Event) error { + var output string + + bot, botErr := tgbotapi.NewBotAPI(token) + if botErr != nil { + return botErr + } + + subject, subjectErr := resolveTemplate(emailSubjectTemplate, event) + if subjectErr != nil { + return subjectErr + } + + body, bodyErr := resolveTemplate(emailBodyTemplate, event) + if bodyErr != nil { + return bodyErr + } + + output = fmt.Sprintf("%s, %s",subject,body) + + msg := tgbotapi.NewMessage(channel, output) + _, sendErr := bot.Send(msg) + + return sendErr +} + +func resolveTemplate(templateValue string, event *types.Event) (string, error) { + var resolved bytes.Buffer + tmpl, err := template.New("test").Parse(templateValue) + if err != nil { + panic(err) + } + err = tmpl.Execute(&resolved, *event) + if err != nil { + panic(err) + } + + return resolved.String(), nil +}