feat: Extract df check config into hcl config file

This commit is contained in:
RouxAntoine 2021-03-22 22:19:50 +01:00
parent a8c0b3ffbb
commit eb9ccad432
Signed by: antoine
GPG Key ID: 098FB66FC0475E70
6 changed files with 81 additions and 63 deletions

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"go/slack-bot/pkg/check" "go/slack-bot/pkg/check"
@ -17,11 +16,13 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclsyntax"
) )
//SlackConf bot global configuration
type SlackConf struct { type SlackConf struct {
BotID string `hcl:"bot_id"` BotID string `hcl:"bot_id"`
DestChannelID string `hcl:"dest_channel_id"` DestChannelID string `hcl:"dest_channel_id"`
WebhookID string `hcl:"webhook_id"` WebhookID string `hcl:"webhook_id"`
DryRun *bool `hcl:"dry_run"` DryRun *bool `hcl:"dry_run"`
Df []check.DfParameter `hcl:"df,block"`
} }
func main() { func main() {
@ -32,40 +33,20 @@ func main() {
conf := ParseConfiguration(configFile) conf := ParseConfiguration(configFile)
slackNotifier := notify.NewSlackNotifier(conf.BotID, conf.DestChannelID, conf.WebhookID) 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 { msg := dfChecker.Check(func(stdout string) []notify.Message {
parsedDf, err := dfChecker.Parse(stdout) var messages []notify.Message
if err != nil { parsedDf := dfChecker.Parse(stdout)
return notify.Message{ casted := parsedDf.(check.DfStdout)
Content: fmt.Sprint("disk is full : ", err), for _, dfCheck := range conf.Df {
ShouldNotify: true, messages = append(messages, dfChecker.FileSystemUsedCheck(casted, dfCheck))
}
} 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,
}
}
} }
return messages
}) })
dryRunCtx := context.WithValue(context.Background(), notify.DryRunContextKey, conf.DryRun) 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 //ParseConfiguration take filename path and parse it to Config.SlackConf

View File

@ -1,3 +1,8 @@
bot_id = "" bot_id = ""
dest_channel_id = "" dest_channel_id = ""
webhook_id = "" webhook_id = ""
df {
fs_name = "<filesystem name>"
critical = "<used pourcent>"
}

View File

@ -14,6 +14,11 @@ resource "kubernetes_config_map" "slack_bot_config" {
bot_id = "${var.bot_id}" bot_id = "${var.bot_id}"
dest_channel_id = "${var.dest_channel_id}" dest_channel_id = "${var.dest_channel_id}"
webhook_id = "${var.webhook_id}" webhook_id = "${var.webhook_id}"
df {
fs_name = "/dev/mapper/vg1-data--kube"
critical = "85"
}
EOF EOF
} }
} }

View File

@ -8,13 +8,13 @@ import (
//Checker representation of check set //Checker representation of check set
type Checker interface { 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 //AdvancedChecker like Checker except contain Parsing step
type AdvancedChecker interface { type AdvancedChecker interface {
Checker Checker
Parse(stdout string) (interface{}, error) Parse(stdout string) interface{}
} }
//CliChecker implement checker with cli command //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 //Check function use to run cli call to check some parameter
func (cc *CliChecker) Check(f func(stdout string) notify.Message) notify.Message { func (cc *CliChecker) Check(f func(stdout string) []notify.Message) []notify.Message {
message := notify.Message{ShouldNotify: false} message := []notify.Message{}
out, err := exec.Command(cc.command[0], cc.command[1:]...).CombinedOutput() out, err := exec.Command(cc.command[0], cc.command[1:]...).CombinedOutput()
if err != nil { if err != nil {
// command fail notify with error // command fail notify with error
message = notify.Message{ message = append(message,
Content: fmt.Sprint(cc.alertingMessage, " : ", string(out), err), notify.Message{
ShouldNotify: true, Content: fmt.Sprint(cc.alertingMessage, " : ", string(out), err),
} ShouldNotify: true,
},
)
} else { } else {
// command success check if condition required notify // command success check if condition required notify
message = f(string(out)) message = f(string(out))

View File

@ -1,19 +1,23 @@
package check package check
import ( import (
"fmt"
"go/slack-bot/pkg/notify"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
) )
const dfMessage = "disk check"
//DfChecker df command implementation of AdvancedChecker //DfChecker df command implementation of AdvancedChecker
type DfChecker struct { type DfChecker struct {
CliChecker CliChecker
pourcentRegexMatcher *regexp.Regexp pourcentRegexMatcher *regexp.Regexp
} }
type DfOut []DfOutLine type DfStdout []DfStdoutLine
type DfOutLine struct { type DfStdoutLine struct {
Filesystem, Filesystem,
Size, Size,
Used, Used,
@ -22,21 +26,26 @@ type DfOutLine struct {
Mounted string Mounted string
} }
//NewDfChecker build new checker for df command //DfParameter df check configuration
func NewDfChecker(alertingMessage string, command ...string) AdvancedChecker { type DfParameter struct {
FileSystemName string `hcl:"fs_name"`
CriticalPourcent int `hcl:"critical"`
}
//NewDfChecker build new checker for df command
func NewDfChecker() *DfChecker {
return &DfChecker{ return &DfChecker{
CliChecker: CliChecker{ CliChecker: CliChecker{
alertingMessage: alertingMessage, alertingMessage: dfMessage,
command: command, command: []string{"bash", "-c", "df | grep -v 'auto_home'"},
}, },
pourcentRegexMatcher: regexp.MustCompile(`(?P<value>.*)%`), pourcentRegexMatcher: regexp.MustCompile(`(?P<value>.*)%`),
} }
} }
//DfParser parse df stdout //Parse df command stdout
func (dc *DfChecker) Parse(stdout string) (interface{}, error) { func (dc *DfChecker) Parse(stdout string) interface{} {
var dfOut DfOut var dfOut DfStdout
for _, line := range strings.Split(stdout, "\n")[1:] { for _, line := range strings.Split(stdout, "\n")[1:] {
if strings.TrimSpace(line) == "" { if strings.TrimSpace(line) == "" {
@ -53,10 +62,10 @@ func (dc *DfChecker) Parse(stdout string) (interface{}, error) {
valueIndex := dc.pourcentRegexMatcher.SubexpIndex("value") valueIndex := dc.pourcentRegexMatcher.SubexpIndex("value")
v, err := strconv.Atoi(dc.pourcentRegexMatcher.FindStringSubmatch(cleanedColumns[4])[valueIndex]) v, err := strconv.Atoi(dc.pourcentRegexMatcher.FindStringSubmatch(cleanedColumns[4])[valueIndex])
if err != nil { if err != nil {
return dfOut, err v = -1
} }
// extract pourcent used from columns // extract pourcent used from columns
dfOut = append(dfOut, DfOutLine{ dfOut = append(dfOut, DfStdoutLine{
Filesystem: cleanedColumns[0], Filesystem: cleanedColumns[0],
Size: cleanedColumns[1], Size: cleanedColumns[1],
Used: cleanedColumns[2], Used: cleanedColumns[2],
@ -65,16 +74,24 @@ func (dc *DfChecker) Parse(stdout string) (interface{}, error) {
Mounted: cleanedColumns[len(cleanedColumns)-1], 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 { for _, line := range dfOut {
if line.Filesystem == fileSystemName { if line.Filesystem == param.FileSystemName {
if line.PourcentUsed > criticalPourcent { if line.PourcentUsed > param.CriticalPourcent {
return true 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
} }

View File

@ -42,10 +42,18 @@ func NewSlackNotifier(BotID, DestChannelID, WebhookID string) *SlackNotifier {
} }
//Notify send notification to channel //Notify send notification to channel
func (sn *SlackNotifier) Notify(ctx context.Context, message Message) { func (sn *SlackNotifier) Notify(ctx context.Context, messages ...Message) {
if message.ShouldNotify { 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{ msg := &slack.WebhookMessage{
Text: message.Content, Text: content,
} }
url := fmt.Sprintf("https://%s/%s/%s/%s", slackHookBaseURL, sn.BotID, sn.DestChannelID, sn.WebhookID) url := fmt.Sprintf("https://%s/%s/%s/%s", slackHookBaseURL, sn.BotID, sn.DestChannelID, sn.WebhookID)