From eb9ccad4323d7effc92b7fecf53433a16f65581f Mon Sep 17 00:00:00 2001 From: RouxAntoine Date: Mon, 22 Mar 2021 22:19:50 +0100 Subject: [PATCH] feat: Extract df check config into hcl config file --- cmd/main.go | 49 +++++++++++++----------------------------- config.sample.hcl | 7 +++++- manifests/common.tf | 5 +++++ pkg/check/command.go | 18 +++++++++------- pkg/check/df.go | 51 +++++++++++++++++++++++++++++--------------- pkg/notify/slack.go | 14 +++++++++--- 6 files changed, 81 insertions(+), 63 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 725a692..5a066c9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "flag" "fmt" "go/slack-bot/pkg/check" @@ -17,11 +16,13 @@ import ( "github.com/hashicorp/hcl/v2/hclsyntax" ) +//SlackConf bot global configuration type SlackConf struct { - BotID string `hcl:"bot_id"` - DestChannelID string `hcl:"dest_channel_id"` - WebhookID string `hcl:"webhook_id"` - DryRun *bool `hcl:"dry_run"` + BotID string `hcl:"bot_id"` + DestChannelID string `hcl:"dest_channel_id"` + WebhookID string `hcl:"webhook_id"` + DryRun *bool `hcl:"dry_run"` + Df []check.DfParameter `hcl:"df,block"` } func main() { @@ -32,40 +33,20 @@ func main() { conf := ParseConfiguration(configFile) slackNotifier := notify.NewSlackNotifier(conf.BotID, conf.DestChannelID, conf.WebhookID) - dfChecker := check.NewDfChecker("disk is full", "bash", "-c", "df | grep -v 'auto_home'") + dfChecker := check.NewDfChecker() - msg := dfChecker.Check(func(stdout string) notify.Message { - parsedDf, err := dfChecker.Parse(stdout) - if err != nil { - return notify.Message{ - Content: fmt.Sprint("disk is full : ", err), - ShouldNotify: true, - } - } else { - casted := parsedDf.(check.DfOut) - shouldNotify := check.FileSystemChecker("devfs", 70, casted) - if shouldNotify { - j, err := json.MarshalIndent(casted, "", " ") - if err != nil { - return notify.Message{ - Content: fmt.Sprint("disk is full : ", err), - ShouldNotify: true, - } - } - return notify.Message{ - Content: fmt.Sprintf("disk is full : ```%s```\n", j), - ShouldNotify: true, - } - } else { - return notify.Message{ - ShouldNotify: false, - } - } + msg := dfChecker.Check(func(stdout string) []notify.Message { + var messages []notify.Message + parsedDf := dfChecker.Parse(stdout) + casted := parsedDf.(check.DfStdout) + for _, dfCheck := range conf.Df { + messages = append(messages, dfChecker.FileSystemUsedCheck(casted, dfCheck)) } + return messages }) dryRunCtx := context.WithValue(context.Background(), notify.DryRunContextKey, conf.DryRun) - slackNotifier.Notify(dryRunCtx, msg) + slackNotifier.Notify(dryRunCtx, msg...) } //ParseConfiguration take filename path and parse it to Config.SlackConf diff --git a/config.sample.hcl b/config.sample.hcl index 9b656df..a53063d 100644 --- a/config.sample.hcl +++ b/config.sample.hcl @@ -1,3 +1,8 @@ bot_id = "" dest_channel_id = "" -webhook_id = "" \ No newline at end of file +webhook_id = "" + +df { + fs_name = "" + critical = "" +} \ No newline at end of file diff --git a/manifests/common.tf b/manifests/common.tf index 07d753b..da144c5 100644 --- a/manifests/common.tf +++ b/manifests/common.tf @@ -14,6 +14,11 @@ resource "kubernetes_config_map" "slack_bot_config" { bot_id = "${var.bot_id}" dest_channel_id = "${var.dest_channel_id}" webhook_id = "${var.webhook_id}" + +df { + fs_name = "/dev/mapper/vg1-data--kube" + critical = "85" +} EOF } } diff --git a/pkg/check/command.go b/pkg/check/command.go index 2496118..f56d240 100644 --- a/pkg/check/command.go +++ b/pkg/check/command.go @@ -8,13 +8,13 @@ import ( //Checker representation of check set type Checker interface { - Check(f func(stdout string) notify.Message) notify.Message + Check(f func(stdout string) []notify.Message) []notify.Message } //AdvancedChecker like Checker except contain Parsing step type AdvancedChecker interface { Checker - Parse(stdout string) (interface{}, error) + Parse(stdout string) interface{} } //CliChecker implement checker with cli command @@ -32,16 +32,18 @@ func NewCliChecker(alertingMessage string, command ...string) Checker { } //Check function use to run cli call to check some parameter -func (cc *CliChecker) Check(f func(stdout string) notify.Message) notify.Message { - message := notify.Message{ShouldNotify: false} +func (cc *CliChecker) Check(f func(stdout string) []notify.Message) []notify.Message { + message := []notify.Message{} out, err := exec.Command(cc.command[0], cc.command[1:]...).CombinedOutput() if err != nil { // command fail notify with error - message = notify.Message{ - Content: fmt.Sprint(cc.alertingMessage, " : ", string(out), err), - ShouldNotify: true, - } + message = append(message, + notify.Message{ + Content: fmt.Sprint(cc.alertingMessage, " : ", string(out), err), + ShouldNotify: true, + }, + ) } else { // command success check if condition required notify message = f(string(out)) diff --git a/pkg/check/df.go b/pkg/check/df.go index a0ec908..eaca35f 100644 --- a/pkg/check/df.go +++ b/pkg/check/df.go @@ -1,19 +1,23 @@ package check import ( + "fmt" + "go/slack-bot/pkg/notify" "regexp" "strconv" "strings" ) +const dfMessage = "disk check" + //DfChecker df command implementation of AdvancedChecker type DfChecker struct { CliChecker pourcentRegexMatcher *regexp.Regexp } -type DfOut []DfOutLine -type DfOutLine struct { +type DfStdout []DfStdoutLine +type DfStdoutLine struct { Filesystem, Size, Used, @@ -22,21 +26,26 @@ type DfOutLine struct { Mounted string } -//NewDfChecker build new checker for df command -func NewDfChecker(alertingMessage string, command ...string) AdvancedChecker { +//DfParameter df check configuration +type DfParameter struct { + FileSystemName string `hcl:"fs_name"` + CriticalPourcent int `hcl:"critical"` +} +//NewDfChecker build new checker for df command +func NewDfChecker() *DfChecker { return &DfChecker{ CliChecker: CliChecker{ - alertingMessage: alertingMessage, - command: command, + alertingMessage: dfMessage, + command: []string{"bash", "-c", "df | grep -v 'auto_home'"}, }, pourcentRegexMatcher: regexp.MustCompile(`(?P.*)%`), } } -//DfParser parse df stdout -func (dc *DfChecker) Parse(stdout string) (interface{}, error) { - var dfOut DfOut +//Parse df command stdout +func (dc *DfChecker) Parse(stdout string) interface{} { + var dfOut DfStdout for _, line := range strings.Split(stdout, "\n")[1:] { if strings.TrimSpace(line) == "" { @@ -53,10 +62,10 @@ func (dc *DfChecker) Parse(stdout string) (interface{}, error) { valueIndex := dc.pourcentRegexMatcher.SubexpIndex("value") v, err := strconv.Atoi(dc.pourcentRegexMatcher.FindStringSubmatch(cleanedColumns[4])[valueIndex]) if err != nil { - return dfOut, err + v = -1 } // extract pourcent used from columns - dfOut = append(dfOut, DfOutLine{ + dfOut = append(dfOut, DfStdoutLine{ Filesystem: cleanedColumns[0], Size: cleanedColumns[1], Used: cleanedColumns[2], @@ -65,16 +74,24 @@ func (dc *DfChecker) Parse(stdout string) (interface{}, error) { Mounted: cleanedColumns[len(cleanedColumns)-1], }) } - return dfOut, nil + return dfOut } -func FileSystemChecker(fileSystemName string, criticalPourcent int, dfOut DfOut) bool { +//FileSystemUsedCheck take dfStdout and check if filesystem is full over than criticalPourcent value +func (dc *DfChecker) FileSystemUsedCheck(dfOut DfStdout, param DfParameter) notify.Message { + message := notify.Message{ + ShouldNotify: false, + } for _, line := range dfOut { - if line.Filesystem == fileSystemName { - if line.PourcentUsed > criticalPourcent { - return true + if line.Filesystem == param.FileSystemName { + if line.PourcentUsed > param.CriticalPourcent { + text := fmt.Sprintf("%s : fs '%s' critial %d%%, used %d%% \n", dc.alertingMessage, line.Filesystem, param.CriticalPourcent, line.PourcentUsed) + message = notify.Message{ + Content: text, + ShouldNotify: true, + } } } } - return false + return message } diff --git a/pkg/notify/slack.go b/pkg/notify/slack.go index 2869e4a..90a6af4 100644 --- a/pkg/notify/slack.go +++ b/pkg/notify/slack.go @@ -42,10 +42,18 @@ func NewSlackNotifier(BotID, DestChannelID, WebhookID string) *SlackNotifier { } //Notify send notification to channel -func (sn *SlackNotifier) Notify(ctx context.Context, message Message) { - if message.ShouldNotify { +func (sn *SlackNotifier) Notify(ctx context.Context, messages ...Message) { + shouldNotify := false + var content string + for _, message := range messages { + if message.ShouldNotify { + shouldNotify = true + content = fmt.Sprintf("%s\n%s", content, message.Content) + } + } + if shouldNotify { msg := &slack.WebhookMessage{ - Text: message.Content, + Text: content, } url := fmt.Sprintf("https://%s/%s/%s/%s", slackHookBaseURL, sn.BotID, sn.DestChannelID, sn.WebhookID)