From 88c7597a3bed54002b27570f7f36b945cffd47ad Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 18 Apr 2023 23:15:01 +0300 Subject: split into main and internal package" --- check.go | 48 ----------------- config.go | 58 -------------------- execute.go | 59 --------------------- internal/check.go | 48 +++++++++++++++++ internal/config.go | 58 ++++++++++++++++++++ internal/execute.go | 59 +++++++++++++++++++++ internal/nagioscode.go | 23 ++++++++ internal/notify.go | 33 ++++++++++++ internal/state.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 30 ----------- nagioscode.go | 23 -------- notify.go | 33 ------------ state.go | 140 ------------------------------------------------- 13 files changed, 361 insertions(+), 391 deletions(-) delete mode 100644 check.go delete mode 100644 config.go delete mode 100644 execute.go create mode 100644 internal/check.go create mode 100644 internal/config.go create mode 100644 internal/execute.go create mode 100644 internal/nagioscode.go create mode 100644 internal/notify.go create mode 100644 internal/state.go delete mode 100644 main.go delete mode 100644 nagioscode.go delete mode 100644 notify.go delete mode 100644 state.go diff --git a/check.go b/check.go deleted file mode 100644 index 492bd98..0000000 --- a/check.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "bytes" - "context" - "os/exec" - "strings" -) - -type check struct { - Plugin string - Args []string -} - -type namedCheck struct { - check - name string -} - -type checkResult struct { - name string - output string - status nagiosCode -} - -func (c check) execute(ctx context.Context, name string) checkResult { - cmd := exec.CommandContext(ctx, c.Plugin, c.Args...) - - var bytes bytes.Buffer - cmd.Stdout = &bytes - cmd.Stderr = &bytes - - if err := cmd.Run(); err != nil { - if ctx.Err() == context.DeadlineExceeded { - return checkResult{name, "Check command timed out", critical} - } - } - - // Remove Nagios perf data from output and trim whitespaces - parts := strings.Split(bytes.String(), "|") - output := strings.TrimSpace(parts[0]) - - return checkResult{name, output, nagiosCode(cmd.ProcessState.ExitCode())} -} - -func (c namedCheck) execute(ctx context.Context) checkResult { - return c.check.execute(ctx, c.name) -} diff --git a/config.go b/config.go deleted file mode 100644 index f54fabc..0000000 --- a/config.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" -) - -type config struct { - EmailTo string - EmailFrom string - SMTPServer string `json:"omitempty"` - StateDir string `json:"omitempty"` - CheckTimeoutS int - CheckConcurrency int - Checks map[string]check -} - -func newConfig(configFile string) (config, error) { - var config config - - // Open the file - file, err := os.Open(configFile) - if err != nil { - return config, err - } - defer file.Close() - - // Read the file content - bytes, err := ioutil.ReadAll(file) - if err != nil { - return config, err - } - - // Parse the JSON content - err = json.Unmarshal(bytes, &config) - if err != nil { - return config, err - } - - if config.SMTPServer == "" { - hostname, err := os.Hostname() - if err != nil { - panic(err) - } - config.SMTPServer = fmt.Sprintf("%s:25", hostname) - log.Println("Set SMTPServer to " + config.SMTPServer) - } - - if config.StateDir == "" { - config.StateDir = "." - log.Println("Set StateDir to " + config.StateDir) - } - - return config, nil -} diff --git a/execute.go b/execute.go deleted file mode 100644 index 7461680..0000000 --- a/execute.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "context" - "log" - "sync" - "time" -) - -func execute(state state, config config) state { - limiterCh := make(chan struct{}, config.CheckConcurrency) - inputCh := make(chan namedCheck) - outputCh := make(chan checkResult) - - go func() { - for name, check := range config.Checks { - inputCh <- namedCheck{check, name} - } - close(inputCh) - }() - - var outputWg sync.WaitGroup - outputWg.Add(1) - - go func() { - for checkResult := range outputCh { - state.update(checkResult) - } - outputWg.Done() - }() - - var inputWg sync.WaitGroup - inputWg.Add(len(config.Checks)) - - for check := range inputCh { - go func(check namedCheck) { - limiterCh <- struct{}{} - defer func() { - <-limiterCh - inputWg.Done() - }() - - ctx, cancel := context.WithTimeout(context.Background(), - time.Duration(config.CheckTimeoutS)*time.Second) - defer cancel() - - outputCh <- check.execute(ctx) - }(check) - } - - inputWg.Wait() - log.Println("All checks completed!") - close(outputCh) - - outputWg.Wait() - log.Println("All outputs collected!") - - return state -} diff --git a/internal/check.go b/internal/check.go new file mode 100644 index 0000000..44e878d --- /dev/null +++ b/internal/check.go @@ -0,0 +1,48 @@ +package internal + +import ( + "bytes" + "context" + "os/exec" + "strings" +) + +type check struct { + Plugin string + Args []string +} + +type namedCheck struct { + check + name string +} + +type checkResult struct { + name string + output string + status nagiosCode +} + +func (c check) execute(ctx context.Context, name string) checkResult { + cmd := exec.CommandContext(ctx, c.Plugin, c.Args...) + + var bytes bytes.Buffer + cmd.Stdout = &bytes + cmd.Stderr = &bytes + + if err := cmd.Run(); err != nil { + if ctx.Err() == context.DeadlineExceeded { + return checkResult{name, "Check command timed out", critical} + } + } + + // Remove Nagios perf data from output and trim whitespaces + parts := strings.Split(bytes.String(), "|") + output := strings.TrimSpace(parts[0]) + + return checkResult{name, output, nagiosCode(cmd.ProcessState.ExitCode())} +} + +func (c namedCheck) execute(ctx context.Context) checkResult { + return c.check.execute(ctx, c.name) +} diff --git a/internal/config.go b/internal/config.go new file mode 100644 index 0000000..39e2209 --- /dev/null +++ b/internal/config.go @@ -0,0 +1,58 @@ +package internal + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" +) + +type config struct { + EmailTo string + EmailFrom string + SMTPServer string `json:"omitempty"` + StateDir string `json:"omitempty"` + CheckTimeoutS int + CheckConcurrency int + Checks map[string]check +} + +func newConfig(configFile string) (config, error) { + var config config + + // Open the file + file, err := os.Open(configFile) + if err != nil { + return config, err + } + defer file.Close() + + // Read the file content + bytes, err := ioutil.ReadAll(file) + if err != nil { + return config, err + } + + // Parse the JSON content + err = json.Unmarshal(bytes, &config) + if err != nil { + return config, err + } + + if config.SMTPServer == "" { + hostname, err := os.Hostname() + if err != nil { + panic(err) + } + config.SMTPServer = fmt.Sprintf("%s:25", hostname) + log.Println("Set SMTPServer to " + config.SMTPServer) + } + + if config.StateDir == "" { + config.StateDir = "." + log.Println("Set StateDir to " + config.StateDir) + } + + return config, nil +} diff --git a/internal/execute.go b/internal/execute.go new file mode 100644 index 0000000..d8f426f --- /dev/null +++ b/internal/execute.go @@ -0,0 +1,59 @@ +package internal + +import ( + "context" + "log" + "sync" + "time" +) + +func execute(state state, config config) state { + limiterCh := make(chan struct{}, config.CheckConcurrency) + inputCh := make(chan namedCheck) + outputCh := make(chan checkResult) + + go func() { + for name, check := range config.Checks { + inputCh <- namedCheck{check, name} + } + close(inputCh) + }() + + var outputWg sync.WaitGroup + outputWg.Add(1) + + go func() { + for checkResult := range outputCh { + state.update(checkResult) + } + outputWg.Done() + }() + + var inputWg sync.WaitGroup + inputWg.Add(len(config.Checks)) + + for check := range inputCh { + go func(check namedCheck) { + limiterCh <- struct{}{} + defer func() { + <-limiterCh + inputWg.Done() + }() + + ctx, cancel := context.WithTimeout(context.Background(), + time.Duration(config.CheckTimeoutS)*time.Second) + defer cancel() + + outputCh <- check.execute(ctx) + }(check) + } + + inputWg.Wait() + log.Println("All checks completed!") + close(outputCh) + + outputWg.Wait() + log.Println("All outputs collected!") + + return state +} diff --git a/internal/nagioscode.go b/internal/nagioscode.go new file mode 100644 index 0000000..b5dc892 --- /dev/null +++ b/internal/nagioscode.go @@ -0,0 +1,23 @@ +package internal + +type nagiosCode int + +const ( + ok nagiosCode = 0 + warning nagiosCode = 1 + critical nagiosCode = 2 + unknown nagiosCode = 3 +) + +func (n nagiosCode) Str() string { + switch n { + case 0: + return "OK" + case 1: + return "WARNING" + case 2: + return "CRITICAL" + default: + return "UNKNOWN" + } +} diff --git a/internal/notify.go b/internal/notify.go new file mode 100644 index 0000000..7d8fc5a --- /dev/null +++ b/internal/notify.go @@ -0,0 +1,33 @@ +package internal + +import ( + "fmt" + "log" + "net/smtp" +) + +func notify(config config, subject, body string) error { + log.Println("notify", subject, body) + + headers := make(map[string]string) + headers["From"] = config.EmailFrom + headers["To"] = config.EmailTo + headers["Subject"] = subject + headers["MIME-Version"] = "1.0" + headers["Content-Type"] = "text/plain; charset=\"utf-8\"" + + header := "" + for k, v := range headers { + header += fmt.Sprintf("%s: %s\r\n", k, v) + } + + message := header + "\r\n" + body + log.Println("Using SMTP server", config.SMTPServer) + + return smtp.SendMail(config.SMTPServer, nil, config.EmailFrom, + []string{config.EmailTo}, []byte(message)) +} + +func notifyError(config config, err error) error { + return notify(config, fmt.Sprintf("GOGIOS: An error occured: %v", err), err.Error()) +} diff --git a/internal/state.go b/internal/state.go new file mode 100644 index 0000000..012bdf5 --- /dev/null +++ b/internal/state.go @@ -0,0 +1,140 @@ +package internal + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "strings" +) + +type checkState struct { + Status nagiosCode + PrevStatus nagiosCode + output string +} + +type state struct { + stateFile string + checks map[string]checkState +} + +func readState(config config) (state, error) { + s := state{ + stateFile: fmt.Sprintf("%s/state.json", config.StateDir), + checks: make(map[string]checkState), + } + + if _, err := os.Stat(s.stateFile); err != nil { + // OK, may be first run with no state yet. + return s, nil + } + + file, err := os.Open(s.stateFile) + if err != nil { + return s, err + } + defer file.Close() + + bytes, err := ioutil.ReadAll(file) + if err != nil { + return s, err + } + + if err := json.Unmarshal(bytes, &s.checks); err != nil { + return s, err + } + + var obsolete []string + for name := range s.checks { + if _, ok := config.Checks[name]; !ok { + obsolete = append(obsolete, name) + } + } + + for _, name := range obsolete { + delete(s.checks, name) + log.Printf("State of %s is obsolete (removed)", name) + } + + return s, nil +} + +func (s state) update(result checkResult) { + prevStatus := unknown + prevState, ok := s.checks[result.name] + if ok { + prevStatus = prevState.Status + } + + checkState := checkState{result.status, prevStatus, result.output} + s.checks[result.name] = checkState + log.Println(result.name, checkState) +} + +func (s state) persist() error { + jsonData, err := json.Marshal(s.checks) + if err != nil { + return err + } + return ioutil.WriteFile(s.stateFile, jsonData, os.ModePerm) +} + +func (s state) report() (string, string, bool) { + var sb strings.Builder + var changed bool + + f := func(filter func(n nagiosCode) bool) int { + var count int + for name, checkState := range s.checks { + if !filter(checkState.Status) { + continue + } + count++ + + if checkState.Status != checkState.PrevStatus { + sb.WriteString(nagiosCode(checkState.PrevStatus).Str()) + sb.WriteString("->") + changed = true + } + + sb.WriteString(nagiosCode(checkState.Status).Str()) + sb.WriteString(": ") + sb.WriteString(name) + sb.WriteString(" ==>> ") + sb.WriteString(checkState.output) + sb.WriteString("\n") + } + + return count + } + + sb.WriteString("This is the recent Gogios report!\n\n") + + numCriticals := f(func(n nagiosCode) bool { return n == 2 }) + if numCriticals > 0 { + sb.WriteString("\n") + } + + numWarnings := f(func(n nagiosCode) bool { return n == 1 }) + if numWarnings > 0 { + sb.WriteString("\n") + } + + numUnknowns := f(func(n nagiosCode) bool { return n > 2 }) + if numUnknowns > 0 { + sb.WriteString("\n") + } + + numOks := f(func(n nagiosCode) bool { return n == 0 }) + if numOks > 0 { + sb.WriteString("\n") + } + + sb.WriteString("Have a nice day!\n") + subject := fmt.Sprintf("GOGIOS Report [C:%d W:%d U:%d OK:%d]", + numCriticals, numWarnings, numUnknowns, numOks) + + return subject, sb.String(), changed +} diff --git a/main.go b/main.go deleted file mode 100644 index 850c37a..0000000 --- a/main.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "flag" -) - -func main() { - configFile := flag.String("cfg", "/etc/gogios.json", "The config file") - flag.Parse() - - config, err := newConfig(*configFile) - if err != nil { - panic(err) - } - - state, err := readState(config) - if err != nil { - notifyError(config, err) - } - - state = execute(state, config) - - if err := state.persist(); err != nil { - notifyError(config, err) - } - - if subject, body, changed := state.report(); changed { - notify(config, subject, body) - } -} diff --git a/nagioscode.go b/nagioscode.go deleted file mode 100644 index 8199aa1..0000000 --- a/nagioscode.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -type nagiosCode int - -const ( - ok nagiosCode = 0 - warning nagiosCode = 1 - critical nagiosCode = 2 - unknown nagiosCode = 3 -) - -func (n nagiosCode) Str() string { - switch n { - case 0: - return "OK" - case 1: - return "WARNING" - case 2: - return "CRITICAL" - default: - return "UNKNOWN" - } -} diff --git a/notify.go b/notify.go deleted file mode 100644 index e546549..0000000 --- a/notify.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/smtp" -) - -func notify(config config, subject, body string) error { - log.Println("notify", subject, body) - - headers := make(map[string]string) - headers["From"] = config.EmailFrom - headers["To"] = config.EmailTo - headers["Subject"] = subject - headers["MIME-Version"] = "1.0" - headers["Content-Type"] = "text/plain; charset=\"utf-8\"" - - header := "" - for k, v := range headers { - header += fmt.Sprintf("%s: %s\r\n", k, v) - } - - message := header + "\r\n" + body - log.Println("Using SMTP server", config.SMTPServer) - - return smtp.SendMail(config.SMTPServer, nil, config.EmailFrom, - []string{config.EmailTo}, []byte(message)) -} - -func notifyError(config config, err error) error { - return notify(config, fmt.Sprintf("GOGIOS: An error occured: %v", err), err.Error()) -} diff --git a/state.go b/state.go deleted file mode 100644 index 197024a..0000000 --- a/state.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - "strings" -) - -type checkState struct { - Status nagiosCode - PrevStatus nagiosCode - output string -} - -type state struct { - stateFile string - checks map[string]checkState -} - -func readState(config config) (state, error) { - s := state{ - stateFile: fmt.Sprintf("%s/state.json", config.StateDir), - checks: make(map[string]checkState), - } - - if _, err := os.Stat(s.stateFile); err != nil { - // OK, may be first run with no state yet. - return s, nil - } - - file, err := os.Open(s.stateFile) - if err != nil { - return s, err - } - defer file.Close() - - bytes, err := ioutil.ReadAll(file) - if err != nil { - return s, err - } - - if err := json.Unmarshal(bytes, &s.checks); err != nil { - return s, err - } - - var obsolete []string - for name := range s.checks { - if _, ok := config.Checks[name]; !ok { - obsolete = append(obsolete, name) - } - } - - for _, name := range obsolete { - delete(s.checks, name) - log.Printf("State of %s is obsolete (removed)", name) - } - - return s, nil -} - -func (s state) update(result checkResult) { - prevStatus := unknown - prevState, ok := s.checks[result.name] - if ok { - prevStatus = prevState.Status - } - - checkState := checkState{result.status, prevStatus, result.output} - s.checks[result.name] = checkState - log.Println(result.name, checkState) -} - -func (s state) persist() error { - jsonData, err := json.Marshal(s.checks) - if err != nil { - return err - } - return ioutil.WriteFile(s.stateFile, jsonData, os.ModePerm) -} - -func (s state) report() (string, string, bool) { - var sb strings.Builder - var changed bool - - f := func(filter func(n nagiosCode) bool) int { - var count int - for name, checkState := range s.checks { - if !filter(checkState.Status) { - continue - } - count++ - - if checkState.Status != checkState.PrevStatus { - sb.WriteString(nagiosCode(checkState.PrevStatus).Str()) - sb.WriteString("->") - changed = true - } - - sb.WriteString(nagiosCode(checkState.Status).Str()) - sb.WriteString(": ") - sb.WriteString(name) - sb.WriteString(" ==>> ") - sb.WriteString(checkState.output) - sb.WriteString("\n") - } - - return count - } - - sb.WriteString("This is the recent Gogios report!\n\n") - - numCriticals := f(func(n nagiosCode) bool { return n == 2 }) - if numCriticals > 0 { - sb.WriteString("\n") - } - - numWarnings := f(func(n nagiosCode) bool { return n == 1 }) - if numWarnings > 0 { - sb.WriteString("\n") - } - - numUnknowns := f(func(n nagiosCode) bool { return n > 2 }) - if numUnknowns > 0 { - sb.WriteString("\n") - } - - numOks := f(func(n nagiosCode) bool { return n == 0 }) - if numOks > 0 { - sb.WriteString("\n") - } - - sb.WriteString("Have a nice day!\n") - subject := fmt.Sprintf("GOGIOS Report [C:%d W:%d U:%d OK:%d]", - numCriticals, numWarnings, numUnknowns, numOks) - - return subject, sb.String(), changed -} -- cgit v1.2.3