Browse Source

feat: Extract df check config into hcl config file

master
RouxAntoine 7 months ago
parent
commit
eb9ccad432
Signed by: antoine <antoinroux@hotmail.fr> GPG Key ID: 098FB66FC0475E70
6 changed files with 81 additions and 63 deletions
  1. +15
    -34
      cmd/main.go
  2. +6
    -1
      config.sample.hcl
  3. +5
    -0
      manifests/common.tf
  4. +10
    -8
      pkg/check/command.go
  5. +34
    -17
      pkg/check/df.go
  6. +11
    -3
      pkg/notify/slack.go

+ 15
- 34
cmd/main.go 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


+ 6
- 1
config.sample.hcl View File

@@ -1,3 +1,8 @@
bot_id = ""
dest_channel_id = ""
webhook_id = ""
webhook_id = ""

df {
fs_name = "<filesystem name>"
critical = "<used pourcent>"
}

+ 5
- 0
manifests/common.tf 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
}
}


+ 10
- 8
pkg/check/command.go 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))


+ 34
- 17
pkg/check/df.go 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
}

+ 11
- 3
pkg/notify/slack.go 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)


Loading…
Cancel
Save