diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0549ec0..49b697b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,8 @@ updates: interval: weekly time: '10:00' open-pull-requests-limit: 10 + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6d83c43..00fc675 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,9 +17,9 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.18 + go-version: 1.21 - name: Test run: go test -v ./... @@ -29,7 +29,7 @@ jobs: - name: Run goreleaser in release mode if: success() && startsWith(github.ref, 'refs/tags/v') - uses: goreleaser/goreleaser-action@v3 + uses: goreleaser/goreleaser-action@v4 with: version: latest args: release --rm-dist diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index d1765fc..1b0d3aa 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -11,13 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.18 + go-version: 1.21 - uses: actions/checkout@v3 - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.51 diff --git a/.golangci.yml b/.golangci.yml index 12fbeec..6a031b3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,36 @@ run: timeout: 5m - skip-files: - - '(.+)_test\.go' - + tests: false linters: - disable-all: false - disable: - - musttag - presets: - - bugs - - unused - fast: false + enable-all: true + disable: + - cyclop + - depguard + - exhaustivestruct + - exhaustruct + - forbidigo + - forcetypeassert + - gci + - gochecknoglobals + - gochecknoinits + - godox + - goerr113 + - gofumpt + - gomnd + - ifshort + - lll + - musttag + - nakedret + - nlreturn + - nolintlint + - nonamedreturns + - tagliatelle + - varnamelen + - wrapcheck +linters-settings: + estif: + min-complexity: 4 + maligned: + suggest-new: true + funlen: + lines: 80 diff --git a/api.go b/api.go index b35c9f7..905e1f5 100644 --- a/api.go +++ b/api.go @@ -7,16 +7,16 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "time" - - log "github.com/sirupsen/logrus" ) type RestAPI struct { URL string Client *http.Client + Logger *slog.Logger } func (a RestAPI) ExecuteCheck(command string, arguments map[string]interface{}, timeout uint32) (*APICheckResult, error) { //nolint:lll @@ -31,14 +31,11 @@ func (a RestAPI) ExecuteCheck(command string, arguments map[string]interface{}, defer cancel() // Build request - requestUrl := a.URL + "/v1/checker?command=" + url.QueryEscape(command) + requestURL := a.URL + "/v1/checker?command=" + url.QueryEscape(command) - log.WithFields(log.Fields{ - "body": string(body), - "url": requestUrl, - }).Debug("sending request") + a.Logger.Debug("sending request", "body", string(body), "url", requestURL) - req, err := http.NewRequestWithContext(ctx, "POST", requestUrl, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("could not build request: %w", err) } @@ -53,6 +50,7 @@ func (a RestAPI) ExecuteCheck(command string, arguments map[string]interface{}, if errors.Is(err, context.DeadlineExceeded) { return nil, fmt.Errorf("timeout during HTTP request: %w", err) } + return nil, fmt.Errorf("executing API request failed: %w", err) } @@ -64,9 +62,9 @@ func (a RestAPI) ExecuteCheck(command string, arguments map[string]interface{}, return nil, fmt.Errorf("could not read result: %w", err) } - log.WithField("body", string(resultBody)).Debug("received response") + a.Logger.Debug("received response", "body", string(resultBody)) - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API request not successful code=%d: %s", resp.StatusCode, string(resultBody)) } diff --git a/api_test.go b/api_test.go index 75f2b9f..e1839cf 100644 --- a/api_test.go +++ b/api_test.go @@ -1,8 +1,10 @@ package main import ( + "log/slog" "net/http" "net/http/httptest" + "os" "strings" "testing" "time" @@ -23,7 +25,7 @@ func TestApiCmd(t *testing.T) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"Invoke-IcingaCheckFoo": {"exitcode": 0, "checkresult": "[OK] \"foo\"", "perfdata": ["'foo'=1.00%;;;0;100 "]}}`)) })), - api: RestAPI{}, + api: RestAPI{Logger: slog.New(slog.NewTextHandler(os.Stdout, nil))}, expected: APICheckResult{ ExitCode: 0, CheckResult: "[OK] \"foo\"", @@ -58,7 +60,13 @@ func TestApiCmd(t *testing.T) { } func TestApiTimeout(t *testing.T) { - api := RestAPI{} + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + + logger := slog.New(slog.NewTextHandler(os.Stdout, opts)) + + api := RestAPI{Logger: logger} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Wait for the context timeout to kick in diff --git a/api_types.go b/api_types.go index 93ab3ad..2740602 100644 --- a/api_types.go +++ b/api_types.go @@ -41,7 +41,7 @@ func (p *APIPerfdataList) UnmarshalJSON(data []byte) error { var value []string // catch empty object and return empty []string - if bytes.Compare(data, []byte("{}")) == 0 { + if bytes.Equal(data, []byte("{}")) { value = []string{} } else { err := json.Unmarshal(data, &value) diff --git a/api_types_test.go b/api_types_test.go index cab01aa..90b4a38 100644 --- a/api_types_test.go +++ b/api_types_test.go @@ -2,8 +2,9 @@ package main import ( "encoding/json" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestAPIPerfdataList_UnmarshalJSON(t *testing.T) { diff --git a/config.go b/config.go index a1bece0..e035c43 100644 --- a/config.go +++ b/config.go @@ -16,19 +16,19 @@ const ( ) type Config struct { - Timeout uint32 API string Command string - Arguments map[string]interface{} CertName string CAFile string + Arguments map[string]interface{} + Timeout uint32 Insecure bool Debug bool PrintVersion bool } var ( - // ErrVersionRequested returned when --version flag is set + // ErrVersionRequested returned when --version flag is set. ErrVersionRequested = errors.New("version was requested by a flag") // ErrNoCommand is returned when no PowerShell command could be parsed from flags. diff --git a/config_test.go b/config_test.go index e38e1b9..067ff1a 100644 --- a/config_test.go +++ b/config_test.go @@ -1,10 +1,11 @@ package main import ( - flag "github.com/spf13/pflag" - "github.com/stretchr/testify/assert" "os" "testing" + + flag "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" ) func quietTest() func() { diff --git a/go.mod b/go.mod index b45e002..562f11f 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ module github.com/NETWAYS/icinga-powershell-connector -go 1.16 +go 1.21 require ( - github.com/NETWAYS/go-check v0.3.0 - github.com/sirupsen/logrus v1.8.1 + github.com/NETWAYS/go-check v0.5.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.1 - golang.org/x/sys v0.1.0 // indirect + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a89cc5f..8a07c33 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,21 @@ -github.com/NETWAYS/go-check v0.3.0 h1:WiLa/POqkI7TWbNxpUNkQ8gd9G0RTCc4DKUvmpy34Hg= -github.com/NETWAYS/go-check v0.3.0/go.mod h1:PO4R+lr+mcdjltcvvJRPnyGbRqMZHlIRDTVIzOnE0HA= +github.com/NETWAYS/go-check v0.5.0 h1:YahXTyveSOww3mTtKnvbsXZSk9kHi82mnvXHsNWx7AQ= +github.com/NETWAYS/go-check v0.5.0/go.mod h1:3G1p9ZCv41UcKirl+nZWne8oKVPUY5HbtyjRwGgyw6A= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/icinga.go b/icinga.go index 8c716de..e0b2da4 100644 --- a/icinga.go +++ b/icinga.go @@ -3,9 +3,8 @@ package main import ( "crypto/x509" "encoding/json" + "log/slog" "os" - - log "github.com/sirupsen/logrus" ) const ( @@ -27,7 +26,7 @@ func LoadIcingaCACert(path string) *x509.CertPool { // Load contents data, err := os.ReadFile(path) if err != nil { - log.WithError(err).WithField("path", path).Debug("could not read Icinga CA certificate") + slog.Error("could not read Icinga CA certificate", "path", path) return nil } @@ -35,7 +34,7 @@ func LoadIcingaCACert(path string) *x509.CertPool { // Build pool pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(data) { - log.WithField("path", path).Warning("could not append any CA certificates to pool") + slog.Error("could not append any CA certificates to pool", "path", path) } return pool @@ -55,7 +54,8 @@ func LoadIcingaVariables(path string) (vars map[string]string) { fh, err := os.Open(path) if err != nil { - log.WithError(err).WithField("path", path).Debug("could not read vars file") + slog.Error("could not read vars file", "path", path) + return nil } diff --git a/icinga_test.go b/icinga_test.go index 87fcf7a..3f31f9f 100644 --- a/icinga_test.go +++ b/icinga_test.go @@ -1,8 +1,9 @@ package main import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestLoadIcingaVariables(t *testing.T) { diff --git a/main.go b/main.go index 935ddc8..c63d0b1 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,10 @@ package main import ( "errors" "fmt" + "log/slog" "os" "github.com/NETWAYS/go-check" - log "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" ) @@ -37,12 +37,25 @@ func main() { check.ExitError(err) } + // Default log options + opts := &slog.HandlerOptions{ + Level: slog.LevelInfo, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + if config.Debug { - log.SetFormatter(&log.TextFormatter{}) - log.SetLevel(log.DebugLevel) + opts.Level = slog.LevelDebug } - api := RestAPI{URL: config.API, Client: config.NewClient()} + logger := slog.New(handler) + slog.SetDefault(logger) + + api := RestAPI{ + URL: config.API, + Client: config.NewClient(), + Logger: logger, + } result, err := api.ExecuteCheck(config.Command, config.Arguments, config.Timeout) if err != nil { diff --git a/netstring.go b/netstring.go index f7abdd9..8b9c796 100644 --- a/netstring.go +++ b/netstring.go @@ -8,7 +8,6 @@ import ( const ( Separator = ':' - // DataEnd = ',' ) func ParseNetstring(r io.Reader) ([]byte, error) { @@ -18,7 +17,7 @@ func ParseNetstring(r io.Reader) ([]byte, error) { digit int ) - // Read length from reader + // Read length from reader. for { _, err := r.Read(char) if err != nil { @@ -33,7 +32,7 @@ func ParseNetstring(r io.Reader) ([]byte, error) { b := char[0] if b == 10 /* \n */ || b == 13 /* \r */ { - // ignore line feeds in length + // ignore line feeds in length. continue } else if b == Separator { break @@ -47,7 +46,7 @@ func ParseNetstring(r io.Reader) ([]byte, error) { length = length*10 + digit } - // Read netstring content + // Read netstring content. data := make([]byte, length) _, err := r.Read(data) @@ -55,7 +54,7 @@ func ParseNetstring(r io.Reader) ([]byte, error) { return nil, fmt.Errorf("failed reading netstring content: %w", err) } - // Read data end char and ignore possible EOF + // Read data end char and ignore possible EOF. _, _ = r.Read(char) return data, nil diff --git a/powershell.go b/powershell.go index f5df48f..24f46bc 100644 --- a/powershell.go +++ b/powershell.go @@ -4,7 +4,8 @@ import ( "strings" ) -// GetPowershellArgs returns remaining args and parse them as if we were powershell.exe +// GetPowershellArgs returns remaining args and parse them as if we were powershell.exe. +// nolint:gocognit func GetPowershellArgs(args []string) (command string, arguments map[string]interface{}) { arguments = map[string]interface{}{} l := len(args) @@ -59,6 +60,7 @@ func GetPowershellArgs(args []string) (command string, arguments map[string]inte return } +// nolint: gocritic func BuildPowershellType(value string) interface{} { if strings.EqualFold(value, `$null`) { return nil @@ -89,7 +91,7 @@ func BuildPowershellType(value string) interface{} { // @('abc') -> []string{"abc"} // @('abc','def') -> []string{"abc","def"} // -// nolint:funlen +// nolint: gocognit func ConvertPowershellArray(value string) []string { if value == "@()" || len(value) == 0 { return []string{} @@ -101,44 +103,44 @@ func ConvertPowershellArray(value string) []string { } // Am I inside of a string - inside_string := false + insideString := false // Should the current character be escaped - escaping_mode := false + escapingMode := false // Which kind of quotes are we using right now? (Could be " or ') - quote_mode := `` + quoteMode := `` result := []string{} // Remember position marker := 0 for i := range value { - if value[i] == '"' && !escaping_mode { - if inside_string && quote_mode == `"` { - inside_string = false - quote_mode = `` + if value[i] == '"' && !escapingMode { + if insideString && quoteMode == `"` { + insideString = false + quoteMode = `` } else { - quote_mode = `"` - inside_string = true + quoteMode = `"` + insideString = true } continue } - if value[i] == '\'' && !escaping_mode { - if inside_string && quote_mode == `'` { - inside_string = false - quote_mode = `` + if value[i] == '\'' && !escapingMode { + if insideString && quoteMode == `'` { + insideString = false + quoteMode = `` } else { - quote_mode = `'` - inside_string = true + quoteMode = `'` + insideString = true } continue } - if value[i] == ',' && !inside_string { + if value[i] == ',' && !insideString { if value[i-1] == ',' { // Two consecutive commas result = append(result, "") @@ -151,13 +153,13 @@ func ConvertPowershellArray(value string) []string { continue } - if value[i] == '\\' && !escaping_mode { - escaping_mode = true + if value[i] == '\\' && !escapingMode { + escapingMode = true continue } - if escaping_mode { - escaping_mode = false + if escapingMode { + escapingMode = false continue } } @@ -171,13 +173,11 @@ func ConvertPowershellArray(value string) []string { } func unquoteString(s string) string { - //fmt.Printf("Unquote string: %s\n", s) if len(s) <= 1 { return s } if ((s[0]) == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') { - //fmt.Printf("Unquote string res: %s\n", s[1:len(s)-1]) return s[1 : len(s)-1] } @@ -191,7 +191,6 @@ func unquoteString(s string) string { // try { Use-Icinga -Minimal; } catch { <# something #> exit 3; }; // Exit-IcingaExecutePlugin -Command 'Invoke-IcingaCheckUsedPartitionSpace' // try { Use-Icinga -Minimal; } catch { <# something #> exit 3; }; Invoke-IcingaCheckUsedPartitionSpace -// Invoke-IcingaCheckUsedPartitionSpace func ParsePowershellTryCatch(command string) string { command = strings.TrimSpace(command) // For now just parse the last word, dequote it and use it as command @@ -200,6 +199,7 @@ func ParsePowershellTryCatch(command string) string { return strings.Trim(parts[len(parts)-1], "'\" \t") } +// nolint:gocognit func IsPowershellArray(s string) bool { l := len(s) if l <= 2 { @@ -214,45 +214,45 @@ func IsPowershellArray(s string) bool { return false } - inside_string := false - escaping_mode := false - quote_mode := `` - found_array_separator := false + insideString := false + escapingMode := false + quoteMode := `` + foundArraySeparator := false for i := range s { - if string(s[i]) == `"` && !escaping_mode { - if inside_string && quote_mode == `"` { - inside_string = false - quote_mode = `` + if string(s[i]) == `"` && !escapingMode { + if insideString && quoteMode == `"` { + insideString = false + quoteMode = `` } else { - quote_mode = `"` - inside_string = true + quoteMode = `"` + insideString = true } continue } - if string(s[i]) == `'` && !escaping_mode { - if inside_string && quote_mode == `'` { - inside_string = false - quote_mode = `` + if string(s[i]) == `'` && !escapingMode { + if insideString && quoteMode == `'` { + insideString = false + quoteMode = `` } else { - quote_mode = `'` - inside_string = true + quoteMode = `'` + insideString = true } continue } - if string(s[i]) == `,` && !inside_string { - found_array_separator = true + if string(s[i]) == `,` && !insideString { + foundArraySeparator = true } - if escaping_mode { - escaping_mode = false + if escapingMode { + escapingMode = false continue } } - return found_array_separator + return foundArraySeparator } diff --git a/version.go b/version.go index c336dfd..6b36882 100644 --- a/version.go +++ b/version.go @@ -4,13 +4,12 @@ import "fmt" // nolint: gochecknoglobals var ( - // These get filled at build time with the proper vaules + // These get filled at build time with the proper vaules. version = "development" commit = "HEAD" date = "latest" ) -//goland:noinspection GoBoolExpressions func buildVersion() string { result := version