diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index feb5a906..f931931e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,13 +35,13 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.21", "1.22", stable] + go-version: ["1.22", "1.23", stable] steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Golang Environment - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version: ${{ matrix.go-version }} @@ -69,10 +69,10 @@ jobs: sed -i 's|\${NGINX_PLUS_VERSION}/||g' docker/Dockerfile - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Build Plus Docker Image - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: file: docker/Dockerfile tags: nginx-plus @@ -90,7 +90,7 @@ jobs: run: docker compose up test-no-stream --exit-code-from test-no-stream - name: Create/Update Draft - uses: lucacome/draft-release@5d29432a46bff6c122cd4b07a1fb94e1bb158d34 # v1.1.1 + uses: lucacome/draft-release@f15262dc3ac8c3efbf09a8ce5406cd0fc47aabb1 # v1.2.2 id: release-notes with: minor-label: "enhancement" @@ -100,15 +100,15 @@ jobs: if: ${{ github.event_name == 'push' }} - name: Setup Golang Environment - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version: stable if: ${{ github.ref_type == 'tag' }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 + uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: - version: v2.6.1 # renovate: datasource=github-tags depName=goreleaser/goreleaser + version: v2.8.2 # renovate: datasource=github-tags depName=goreleaser/goreleaser args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 04a681cb..16efcde5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -51,14 +51,14 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Golang Environment - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version: stable if: matrix.language == 'go' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -70,6 +70,6 @@ jobs: # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index d1be1aff..073f91df 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -27,6 +27,6 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Dependency Review - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 + uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0 with: config-file: "nginx/k8s-common/dependency-review-config.yml@main" diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index ce730445..3dec0515 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -24,6 +24,6 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Scan - uses: fossas/fossa-action@93a52ecf7c3ac7eb40f5de77fd69b1a19524de94 # v1.5.0 + uses: fossas/fossa-action@c0a7d013f84c8ee5e910593186598625513cc1e4 # v1.6.0 with: api-key: ${{ secrets.FOSSA_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 111e99f6..abb49312 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,14 +28,14 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Golang Environment - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version: stable - name: Lint Go - uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0 + uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 with: - version: v1.63.4 # renovate: datasource=github-tags depName=golangci/golangci-lint + version: v2.1.5 # renovate: datasource=github-tags depName=golangci/golangci-lint actionlint: name: Actionlint @@ -45,7 +45,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Lint Actions - uses: reviewdog/action-actionlint@abd537417cf4991e1ba8e21a67b1119f4f53b8e0 # v1.64.1 + uses: reviewdog/action-actionlint@a5524e1c19e62881d79c1f1b9b6f09f16356e281 # v1.65.2 with: actionlint_flags: -shellcheck "" @@ -70,4 +70,4 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Lint YAML - uses: reviewdog/action-yamllint@1dca3ad811867be18fbe293a9818d715a6c2cd46 # v1.20.0 + uses: reviewdog/action-yamllint@f01d8a48fd8d89f89895499fca2cff09f9e9e8c0 # v1.21.0 diff --git a/.github/workflows/notifications.yml b/.github/workflows/notifications.yml index dfaeaf0d..bb13506e 100644 --- a/.github/workflows/notifications.yml +++ b/.github/workflows/notifications.yml @@ -44,7 +44,7 @@ jobs: } - name: Send Notification - uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 + uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e # v3.18.0 with: status: custom custom_payload: | diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 1c9cc615..e3dec9e7 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -31,7 +31,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -49,7 +49,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: sarif_file: results.sarif diff --git a/.golangci.yml b/.golangci.yml index 0624468a..07d01b78 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,44 +1,6 @@ -linters-settings: - misspell: - locale: US - revive: - ignore-generated-header: true - rules: - - name: blank-imports - - name: constant-logical-expr - - name: context-as-argument - - name: context-keys-type - - name: defer - - name: dot-imports - - name: duplicated-imports - - name: empty-block - - name: error-naming - - name: error-return - - name: error-strings - - name: errorf - - name: exported - - name: import-shadowing - - name: increment-decrement - - name: indent-error-flow - - name: package-comments - - name: range - - name: range-val-address - - name: range-val-in-closure - - name: receiver-naming - - name: redefines-builtin-id - - name: string-of-int - - name: superfluous-else - - name: time-naming - - name: unchecked-type-assertion - - name: unexported-return - - name: unnecessary-stmt - - name: unreachable-code - - name: unused-parameter - - name: var-declaration - - name: var-naming - govet: - enable-all: true +version: "2" linters: + default: none enable: - asasalint - asciicheck @@ -59,11 +21,7 @@ linters: - gochecksumtype - gocritic - godot - - gofmt - - gofumpt - - goimports - gosec - - gosimple - gosmopolitan - govet - ineffassign @@ -75,19 +33,16 @@ linters: - nilerr - noctx - nolintlint + - paralleltest - perfsprint - prealloc - predeclared - - paralleltest - reassign - revive - staticcheck - - stylecheck - tagalign - - tenv - thelper - tparallel - - typecheck - unconvert - unparam - unused @@ -95,9 +50,67 @@ linters: - wastedassign - whitespace - wrapcheck - disable-all: true + settings: + govet: + enable-all: true + misspell: + locale: US + revive: + rules: + - name: blank-imports + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: defer + - name: dot-imports + - name: duplicated-imports + - name: empty-block + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: import-shadowing + - name: increment-decrement + - name: indent-error-flow + - name: package-comments + - name: range + - name: range-val-address + - name: range-val-in-closure + - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else + - name: time-naming + - name: unchecked-type-assertion + - name: unexported-return + - name: unnecessary-stmt + - name: unreachable-code + - name: unused-parameter + - name: var-declaration + - name: var-naming + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 -run: - timeout: 5m +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c0f8156..a52aa5d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,12 +24,12 @@ repos: - id: detect-private-key - repo: https://github.com/golangci/golangci-lint - rev: v1.63.4 + rev: v2.1.5 hooks: - id: golangci-lint-full - repo: https://github.com/gitleaks/gitleaks - rev: v8.23.2 + rev: v8.24.3 hooks: - id: gitleaks @@ -39,7 +39,7 @@ repos: - id: markdownlint-cli2 - repo: https://github.com/adrienverge/yamllint.git - rev: v1.35.1 + rev: v1.37.0 hooks: - id: yamllint diff --git a/Makefile b/Makefile index 87974328..33e974d8 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ # renovate: datasource=github-tags depName=golangci/golangci-lint -GOLANGCI_LINT_VERSION = v1.63.4 +GOLANGCI_LINT_VERSION = v2.1.5 test: unit-test test-integration test-integration-no-stream-block clean lint: - go run github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run --fix + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run --fix unit-test: go test -v -shuffle=on -race client/*.go diff --git a/client/nginx.go b/client/nginx.go index 4a44d98c..ebe59212 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -52,6 +52,15 @@ var ( ErrPlusVersionNotFound = errors.New("plus version not found in the input string") ) +// StatusError is an interface that defines our API with consumers of the plus client errors. +// The error will return a http status code and an NGINX error code. +type StatusError interface { + Status() int + Code() string +} + +var _ StatusError = (*internalError)(nil) + // NginxClient lets you access NGINX Plus API. type NginxClient struct { httpClient *http.Client @@ -112,8 +121,18 @@ type apiError struct { } type internalError struct { - err string - apiError + err string + apiError apiError +} + +// Status returns the HTTP status code of the error. +func (internalError *internalError) Status() int { + return internalError.apiError.Status +} + +// Status returns the NGINX error code on the response. +func (internalError *internalError) Code() string { + return internalError.apiError.Code } // Error allows internalError to match the Error interface. @@ -1782,7 +1801,7 @@ func (client *NginxClient) GetStreamServerZones(ctx context.Context) (*StreamSer if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &zones, nil } } @@ -1808,7 +1827,7 @@ func (client *NginxClient) GetStreamUpstreams(ctx context.Context) (*StreamUpstr if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &upstreams, nil } } @@ -1824,7 +1843,7 @@ func (client *NginxClient) GetStreamZoneSync(ctx context.Context) (*StreamZoneSy if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return nil, nil } } @@ -2137,7 +2156,7 @@ func (client *NginxClient) GetStreamConnectionsLimit(ctx context.Context) (*Stre if err != nil { var ie *internalError if errors.As(err, &ie) { - if ie.Code == pathNotFoundCode { + if ie.Code() == pathNotFoundCode { return &limitConns, nil } } diff --git a/client/nginx_test.go b/client/nginx_test.go index 46467477..6d1661a0 100644 --- a/client/nginx_test.go +++ b/client/nginx_test.go @@ -3,6 +3,7 @@ package client import ( "context" "encoding/json" + "errors" "net/http" "net/http/httptest" "reflect" @@ -676,8 +677,8 @@ func TestClientWithMaxAPI(t *testing.T) { t.Parallel() // Test creating a new client with max API version ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.RequestURI == "/": + switch r.RequestURI { + case "/": _, err := w.Write([]byte(tt.apiVersions)) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -851,8 +852,8 @@ func TestGetStats_SSL(t *testing.T) { func TestGetMaxAPIVersionServer(t *testing.T) { t.Parallel() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.RequestURI == "/": + switch r.RequestURI { + case "/": _, err := w.Write([]byte(`[4, 5, 6, 7]`)) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -883,8 +884,8 @@ func TestGetMaxAPIVersionServer(t *testing.T) { func TestGetMaxAPIVersionClient(t *testing.T) { t.Parallel() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.RequestURI == "/": + switch r.RequestURI { + case "/": _, err := w.Write([]byte(`[4, 5, 6, 7, 8, 9, 25]`)) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -1438,6 +1439,70 @@ func TestUpdateStreamServers(t *testing.T) { } } +func TestInternalError(t *testing.T) { + t.Parallel() + + // mimic a user-defined interface type + type TestStatusError interface { + Status() int + Code() string + } + + //nolint // ignore golangci-lint err113 sugggestion to create package level static error + anotherErr := errors.New("another error") + + notFoundErr := &internalError{ + err: "not found error", + apiError: apiError{ + Text: "not found error", + Status: http.StatusNotFound, + Code: "not found code", + }, + } + + testcases := map[string]struct { + inputErr error + expectedCode string + expectedStatus int + }{ + "simple not found": { + inputErr: notFoundErr, + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + "not found joined with another error": { + inputErr: errors.Join(notFoundErr, anotherErr), + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + "not found wrapped with another error": { + inputErr: notFoundErr.Wrap("some error"), + expectedStatus: http.StatusNotFound, + expectedCode: "not found code", + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + var se TestStatusError + ok := errors.As(tc.inputErr, &se) + if !ok { + t.Fatalf("could not cast error %v as StatusError", tc.inputErr) + } + + if se.Status() != tc.expectedStatus { + t.Fatalf("expected status %d, got status %d", tc.expectedStatus, se.Status()) + } + + if se.Code() != tc.expectedCode { + t.Fatalf("expected code %s, got code %s", tc.expectedCode, se.Code()) + } + }) + } +} + type response struct { servers interface{} statusCode int diff --git a/compose.yaml b/compose.yaml index d1b0433c..c68d0f67 100644 --- a/compose.yaml +++ b/compose.yaml @@ -29,7 +29,7 @@ services: service: nginx test: - image: golang:1.23 + image: golang:1.24 volumes: - type: bind source: ./ diff --git a/docker/Dockerfile b/docker/Dockerfile index dd321a34..f31000aa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.13 +# syntax=docker/dockerfile:1.15 FROM debian:12-slim LABEL maintainer="NGINX Docker Maintainers " diff --git a/go.mod b/go.mod index a94427da..90276c54 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/nginx/nginx-plus-go-client/v2 -go 1.22.6 +go 1.24.2 -require golang.org/x/sync v0.10.0 +require golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index cf16d914..67fee8ff 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=