-
Notifications
You must be signed in to change notification settings - Fork 3
Rewrite in Golang #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| .idea | ||
| \#* | ||
| .\#* | ||
|
|
||
| vendor/ | ||
| coverage.html | ||
| coverage.out | ||
| dist/ | ||
|
|
||
| check_sms3status |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| version: "2" | ||
| run: | ||
| tests: false | ||
| linters: | ||
| default: all | ||
| enable: | ||
| - wsl_v5 | ||
| - gomodguard_v2 | ||
| disable: | ||
| - wsl | ||
| - gomodguard | ||
| - cyclop | ||
| - depguard | ||
| - err113 | ||
| - exhaustruct | ||
| - forbidigo | ||
| - forcetypeassert | ||
| - gochecknoglobals | ||
| - gochecknoinits | ||
| - godot | ||
| - godox | ||
| - lll | ||
| - mnd | ||
| - musttag | ||
| - nakedret | ||
| - nlreturn | ||
| - nolintlint | ||
| - nonamedreturns | ||
| - tagliatelle | ||
| - varnamelen | ||
| - wrapcheck | ||
| - funlen | ||
| settings: | ||
| wsl_v5: | ||
| allow-first-in-block: true | ||
| allow-whole-block: true | ||
| branch-max-lines: 2 | ||
| disable: | ||
| - err | ||
| exclusions: | ||
| generated: lax | ||
| presets: | ||
| - comments | ||
| - common-false-positives | ||
| - legacy | ||
| - std-error-handling | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ | ||
| formatters: | ||
| enable: | ||
| - gofmt | ||
| - goimports | ||
| exclusions: | ||
| generated: lax | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| .PHONY: test coverage lint vet | ||
|
|
||
| build: | ||
| CGO_ENABLED=0 go build | ||
| lint: | ||
| go fmt $(go list ./... | grep -v /vendor/) | ||
| vet: | ||
| go vet $(go list ./... | grep -v /vendor/) | ||
| test: | ||
| go test -v -cover ./... | ||
| coverage: | ||
| go test -v -cover -coverprofile=coverage.out ./... &&\ | ||
| go tool cover -html=coverage.out -o coverage.html |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,31 @@ | ||
| check_sms3status | ||
| ================ | ||
| # check_sms3status | ||
|
|
||
| This plugin checks the status of an SMS modem using the regular_run functionality | ||
| provided in smstools3 | ||
| A check plugin for SMS modem using the regular_run functionality provided by [smstools3](https://smstools3.kekekasvi.com/) | ||
|
|
||
| It does not directly access the modem, but instead reads a status file generated | ||
| by smstools3. | ||
| It does not directly access the modem, instead it reads the status file generated by smstools3. | ||
|
|
||
| In order to work the following options need to be set in smsd.conf | ||
| In order to work the following options need to be set in `smsd.conf`: | ||
|
|
||
| regular_run_interval = 60 | ||
| regular_run_cmd = AT+CREG?;+CSQ;+COPS? | ||
| regular_run_statfile = F<status_file> | ||
| ``` | ||
| regular_run_interval = 60 | ||
| regular_run_cmd = AT+CREG?;+CSQ;+COPS? | ||
| regular_run_statfile = F<status_file> | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Requirements | ||
| ``` | ||
| Flags: | ||
| -a, --age int The maximum age of the file in seconds (default 300) (default 300) | ||
| -c, --critical string Critical threshold for signal strength in percent (default "20:") | ||
| -h, --help help for check_sms3status | ||
| -s, --statusfile string Path to the status file | ||
| -v, --version version for check_sms3status | ||
| -w, --warning string Warning threshold for signal strength in percent (default "40:") | ||
| ``` | ||
|
|
||
| * Perl library: `Nagios::Plugins utils.pm` | ||
|
|
||
| ### Usage | ||
|
|
||
| check_sms3status [options] status_file | ||
|
|
||
| --warning | ||
| warning level for percentage signal strength (default 40) | ||
|
|
||
| --critical | ||
| critical level for percentage signal strength (default 20) | ||
|
|
||
| --timeout | ||
| how long to wait for the file (default 30) | ||
|
|
||
| --age | ||
| the maximum age of the file in seconds (default 300) | ||
| ## Examples | ||
|
|
||
| ```bash | ||
| check_sms3status --warning 30 --critical 50 --statusfile /path/to/statusfile | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math" | ||
| "os" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/NETWAYS/go-check" | ||
| "github.com/NETWAYS/go-check/result" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var warning, critical, statusfilePath string | ||
| var maxFileAge int64 | ||
|
|
||
| var csqRe = regexp.MustCompile(`\+CSQ:\s*(\d+),(\d+)`) | ||
| var copsReQuoted = regexp.MustCompile(`\+COPS:\s*\d+,\d+,"([^"]*)"`) | ||
| var copsReUnquoted = regexp.MustCompile(`\+COPS:\s*\d+,\d+,([^"]+)`) | ||
| var cregRe = regexp.MustCompile(`\+CREG:\s*(\d,(?:1|5))`) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could those be |
||
|
|
||
| var rootCmd = &cobra.Command{ | ||
| Use: "check_sms3status", | ||
| Short: "This plugin checks the status of an SMS modem using the regular_run functionality provided in smstools3", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think some prefix for "plugin" would be good. What kind of "plugin" is it? For what? Assume someone might find this without a big Icinga/Nagios/Naemon background. |
||
| Long: `This plugin checks the status of an SMS modem using the regular_run functionality provided in smstools3a. It does not directly access the modem, but instead reads a status file generated by smstools3.`, | ||
| Example: ` | ||
| check_sms3status --warning 30 --critical 50 --statusfile /path/to/statusfile | ||
| `, | ||
| Run: func(_ *cobra.Command, _ []string) { | ||
| // Parse the thresholds add exit if there's an issue | ||
| warnThreshold, warningThresholdErr := check.ParseThreshold(warning) | ||
|
|
||
| if warningThresholdErr != nil { | ||
| check.ExitError(warningThresholdErr) | ||
| } | ||
|
|
||
| critThreshold, critThresholdErr := check.ParseThreshold(critical) | ||
|
|
||
| if critThresholdErr != nil { | ||
| check.ExitError(critThresholdErr) | ||
| } | ||
|
|
||
| // Get the file content and info | ||
| content, fileInfo, contentErr := getFileContentAndInfo(statusfilePath) | ||
|
|
||
| if contentErr != nil { | ||
| check.ExitError(contentErr) | ||
| } | ||
|
|
||
| var overall result.Overall | ||
|
|
||
| overall.AddSubcheck(checkFileAge(fileInfo)) | ||
| overall.AddSubcheck(checkContent(string(content), *warnThreshold, *critThreshold)) | ||
|
|
||
| check.Exit(overall.GetStatus(), overall.GetOutput()) | ||
| }, | ||
| } | ||
|
|
||
| func Execute(version string) { | ||
| defer check.CatchPanic() | ||
|
|
||
| rootCmd.Version = version | ||
| rootCmd.VersionTemplate() | ||
|
|
||
| err := rootCmd.Execute() | ||
| if err != nil { | ||
| check.ExitError(err) | ||
| } | ||
| } | ||
|
|
||
| func init() { | ||
| rootCmd.Flags().StringVarP(&statusfilePath, "statusfile", "s", "", "Path to the status file") | ||
| rootCmd.Flags().StringVarP(&warning, "warning", "w", "40:", "Warning threshold for signal strength in percent") | ||
| rootCmd.Flags().StringVarP(&critical, "critical", "c", "20:", "Critical threshold for signal strength in percent") | ||
| rootCmd.Flags().Int64VarP(&maxFileAge, "age", "a", 300, "The maximum age of the file in seconds (default 300)") | ||
|
|
||
| _ = rootCmd.MarkFlagRequired("statusfile") | ||
| } | ||
|
|
||
| func Usage(cmd *cobra.Command, _ []string) { | ||
| _ = cmd.Usage() | ||
|
|
||
| os.Exit(3) | ||
| } | ||
|
|
||
| // getFileContentAndInfo returns the files content and file info | ||
| func getFileContentAndInfo(filePath string) ([]byte, os.FileInfo, error) { | ||
| content, contentErr := os.ReadFile(filePath) | ||
|
|
||
| var fileInfo os.FileInfo | ||
|
|
||
| if contentErr != nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error check should be directly below the statement that might cause the error. |
||
| return content, fileInfo, contentErr | ||
| } | ||
|
|
||
| fileInfo, fileInfoErr := os.Stat(filePath) | ||
| if fileInfoErr != nil { | ||
| return content, fileInfo, fileInfoErr | ||
| } | ||
|
|
||
| return content, fileInfo, nil | ||
| } | ||
|
|
||
| // checkFileAge compares the given file into with the requested maximum file age | ||
| func checkFileAge(fileInfo os.FileInfo) *result.PartialResult { | ||
| nowSec := time.Now().Unix() | ||
| fileSec := fileInfo.ModTime().Unix() | ||
| fileAge := nowSec - fileSec | ||
|
|
||
| tmpResult := &result.PartialResult{ | ||
| Output: fmt.Sprintf("Status file was last updated %d seconds ago", fileAge), | ||
| } | ||
|
|
||
| tmpResult.SetState(check.OK) | ||
|
|
||
| if fileAge > maxFileAge { | ||
| tmpResult.SetState(check.Critical) | ||
| } | ||
|
|
||
| return tmpResult | ||
| } | ||
|
|
||
| // checkContent compares the file content against the given thresholds | ||
| func checkContent(fileContent string, warningThreshold check.Threshold, criticalThreshold check.Threshold) *result.PartialResult { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should return an unknown if the file content is not a statfile |
||
| var signal, sigber int | ||
|
|
||
| var network string | ||
|
|
||
| isRegistered := true | ||
|
|
||
| lines := strings.SplitSeq(fileContent, "\n") | ||
|
|
||
| for line := range lines { | ||
| if matches := csqRe.FindStringSubmatch(line); matches != nil { | ||
| val, err := strconv.Atoi(matches[1]) | ||
| if err == nil { | ||
| signal = val | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is no match for the regex, |
||
|
|
||
| val, err = strconv.Atoi(matches[2]) | ||
| if err == nil { | ||
| sigber = val | ||
| } | ||
| } else if matches := copsReQuoted.FindStringSubmatch(line); matches != nil { | ||
| network = matches[1] | ||
| } else if matches := copsReUnquoted.FindStringSubmatch(line); matches != nil { | ||
| network = matches[1] | ||
| } | ||
|
|
||
| if strings.Contains(line, "+CREG:") && !cregRe.MatchString(line) { | ||
| isRegistered = false | ||
| } | ||
| } | ||
|
|
||
| sigdb := (2 * signal) - 113 | ||
| sigproc := float64(signal*100) / 31.0 | ||
| result := result.NewPartialResult() | ||
|
|
||
| if !isRegistered { | ||
| result.Output = "Modem not registered on network" | ||
| result.SetState(check.Critical) | ||
|
|
||
| return result | ||
| } else if signal > 31 { | ||
| result.Output = "Signal strength returned an invalid value" | ||
| result.SetState(check.Unknown) | ||
| } | ||
|
|
||
| result.Perfdata.Add(&check.Perfdata{Label: "dbm", Value: sigdb, Uom: "", Warn: nil, Crit: nil, Min: nil, Max: nil}) | ||
| result.Perfdata.Add(&check.Perfdata{Label: "signal", Value: math.Round(sigproc), Uom: "%", Warn: &warningThreshold, Crit: &criticalThreshold}) | ||
|
|
||
| msg := fmt.Sprintf("Registered on network '%s' with signal strength %0.f%%", network, sigproc) | ||
|
|
||
| result.Output = msg | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just |
||
|
|
||
| // bit error rate 99 means unknown | ||
| if sigber != 99 { | ||
| result.Perfdata.Add(&check.Perfdata{Label: "bit_error_rate", Value: sigber}) | ||
| } | ||
|
|
||
| //nolint: gocritic | ||
| if criticalThreshold.DoesViolate(sigproc) { | ||
| result.SetState(check.Critical) | ||
| } else if warningThreshold.DoesViolate(sigproc) { | ||
| result.SetState(check.Warning) | ||
| result.Output = msg | ||
| } else { | ||
| result.SetState(check.OK) | ||
| } | ||
|
|
||
| return result | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please put those variables in a config
structso it obvious that they belong together and share a purpose.