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 (
"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

View File

@ -1,3 +1,8 @@
bot_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}"
dest_channel_id = "${var.dest_channel_id}"
webhook_id = "${var.webhook_id}"
df {
fs_name = "/dev/mapper/vg1-data--kube"
critical = "85"
}
EOF
}
}

View File

@ -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))

View File

@ -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<value>.*)%`),
}
}
//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
}

View File

@ -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)