From 51f700fba71c7579144ebac0361b388678ae9699 Mon Sep 17 00:00:00 2001 From: Michael Weibel Date: Fri, 3 Jul 2026 17:36:26 +0200 Subject: [PATCH] chore: add golangci-lint --- .github/workflows/lint.yml | 31 +++++ .gitignore | 1 + .golangci.yml | 97 ++++++++++++++ Makefile | 120 ++++++++++++++++-- cloudscale.go | 10 +- cloudscale_test.go | 14 +- ctxutil_test.go | 5 +- custom_image_imports_test.go | 13 +- custom_images.go | 4 - custom_images_test.go | 5 +- flavors_test.go | 3 +- floating_ips_test.go | 17 +-- generic_service.go | 3 +- go.mod | 2 +- instrumentation/instrumentation_test.go | 30 ++--- instrumentation/metrics.go | 2 +- load_balancer_health_monitors.go | 4 +- load_balancer_listeners_test.go | 4 +- metadata.go | 61 +++++---- metadata_json.go | 4 +- metadata_test.go | 9 +- metrics_test.go | 4 +- networks.go | 4 - networks_test.go | 12 +- objects_users_test.go | 14 +- regions_test.go | 3 +- server_groups_test.go | 12 +- servers.go | 2 +- servers_test.go | 15 +-- subnets.go | 4 - subnets_test.go | 5 +- tags_test.go | 2 +- test/integration/cloudscale_test.go | 3 +- .../custom_images_integration_test.go | 13 +- test/integration/flavors_integration_test.go | 3 +- .../floating_ips_integration_test.go | 5 +- test/integration/helper.go | 15 +-- ...lancer_health_monitors_integration_test.go | 20 +-- ...oad_balancer_listeners_integration_test.go | 6 +- ..._balancer_pool_members_integration_test.go | 10 +- .../load_balancer_pools_integration_test.go | 8 +- .../load_balancers_integration_test.go | 14 +- test/integration/metrics_integration_test.go | 4 +- test/integration/networks_integration_test.go | 8 +- .../objects_users_integration_test.go | 13 +- test/integration/regions_integration_test.go | 3 +- .../server_groups_integration_test.go | 5 +- test/integration/servers_integration_test.go | 8 +- test/integration/subnets_integration_test.go | 12 +- test/integration/tags_integration_test.go | 32 ++--- .../volume_snapshots_integration_test.go | 6 +- test/integration/volumes_integration_test.go | 8 +- volumes.go | 3 +- 53 files changed, 466 insertions(+), 249 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..a4b19a42 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: Lint + +permissions: {} + +on: + push: + branches: [main] + pull_request: + +jobs: + lint: + permissions: + contents: read + name: Run on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + + - name: Check linter configuration + run: make lint-config + - name: Run linter + run: make lint \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1ddcf913..dcefd757 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ /vendor/ +bin/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..eae018a7 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,97 @@ +version: "2" +run: + allow-parallel-runners: true + build-tags: + - integration +linters: + default: none + enable: + - asasalint # warns about passing []any to func(...any) without expanding it + - asciicheck # non ascii symbols + - copyloopvar # copying loop variables + - errcheck # unchecked errors + - exhaustive # exhaustiveness of enum switch statements + - ginkgolinter # ginkgo and gomega - only if ginkgo/gomega is used + - gocritic # bugs, performance, style + - gocyclo # cyclomatic complexity of functions + - godoclint # go documentation linting against best practices + - gosec # potential security problems + - govet # basically 'go vet' + - importas # enforces consistent import aliases. + - ineffassign # ineffectual assignments + - loggercheck # check for even key/value pairs in logger calls + - modernize # suggest simplifications to Go code, using modern language and library features + - misspell # spelling checks + - nakedret # naked returns (named return parameters and an empty return) + - noctx # http requests without context.Context + - nolintlint # badly formatted nolint directives + - predeclared # shadowing predeclared identifiers + - promlinter # only if prom is used for creating metrics + - revive # better version of golint + - spancheck # only if OpenTelemetry is used + - staticcheck # many static checks + - thelper # test helpers not starting with t.Helper() + - unconvert # unnecessary type conversions + - unparam # unused function parameters + - unused # unused constants, variables,functions, types + - usestdlibvars # using variables/constants from the standard library + - usetesting # reports uses of functions with replacement inside the testing package + - whitespace # unnecessary newlines + settings: + loggercheck: + kitlog: false + slog: false + zap: false + require-string-key: true + no-printf-like: true + modernize: + disable: + - omitzero + gocritic: + disabled-checks: + - ifElseChain # disabled ifElseChain because this is purely stylistic + revive: + rules: + # The following rules are recommended https://github.com/mgechev/revive#recommended-configuration + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: error-return + - name: error-strings + - name: error-naming + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unreachable-code + - name: redefines-builtin-id + staticcheck: + dot-import-whitelist: + - fmt + - github.com/onsi/gomega + - github.com/onsi/ginkgo/v2 + exclusions: + generated: lax + warn-unused: true +formatters: + enable: + - goimports # ensures imports are organized + - gofmt # Check if the code is formatted according to 'gofmt' command. + settings: + goimports: + # A list of prefixes, which, if set, checks import paths + # with the given prefixes are grouped after 3rd-party packages. + # Default: [] + local-prefixes: + - github.com/cloudscale-ch/cloudscale-go-sdk + exclusions: + generated: lax + warn-unused: true \ No newline at end of file diff --git a/Makefile b/Makefile index f9f43108..95e85b98 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,78 @@ VERSION ?= $(shell cat VERSION) TESTARGS?= -test: +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN := $(shell pwd)/bin +$(LOCALBIN): + mkdir -p "$(LOCALBIN)" + +# Host OS/ARCH used to namespace tool binaries in $(LOCALBIN) +HOST_PLATFORM := $(shell go env GOOS)-$(shell go env GOARCH) + +## Tool Binaries +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GOVULNCHECK ?= $(LOCALBIN)/govulncheck + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk command is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: test +test: ## Run unit tests go test -race -v ./... $(TESTARGS) -timeout 30s -clean-testcache: - @go clean -testcache # Force retesting of code +.PHONY: clean-testcache +clean-testcache: ## Force retesting of code + @go clean -testcache -integration: clean-testcache +.PHONY: integration +integration: clean-testcache ## Run integration tests go test -tags=integration -v ./test/integration/... $(TESTARGS) -parallel 4 -timeout 120m -integration-short: clean-testcache +.PHONY: integration-short +integration-short: clean-testcache ## Run quick integration tests go test -tags=integration -short -v ./test/integration/... $(TESTARGS) -parallel 4 -timeout 120m -vet: +.PHONY: vet +vet: ## Run go vet against code. go vet ./... go vet -tags=integration ./test/integration/ -fmt: +.PHONY: fmt +fmt: ## Run go fmt against code. go fmt gofmt -l -w test/integration +.PHONY: bump-version bump-version: @[ "${NEW_VERSION}" ] || ( echo "NEW_VERSION must be set (ex. make NEW_VERSION=v1.x.x bump-version)"; exit 1 ) @(echo ${NEW_VERSION} | grep -E "^v") || ( echo "NEW_VERSION must be a semver ('v' prefix is required)"; exit 1 ) @@ -29,4 +81,56 @@ bump-version: @sed -i.bak -e 's/${VERSION}/${NEW_VERSION}/g' cloudscale.go @rm cloudscale.go.bak -.PHONY: test vet integration integration-short clean-testcache +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter + "$(GOLANGCI_LINT)" run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + "$(GOLANGCI_LINT)" run --fix + +.PHONY: lint-config +lint-config: golangci-lint ## Verify golangci-lint linter configuration + "$(GOLANGCI_LINT)" config verify + +.PHONY: govulncheck +govulncheck: govulncheck-tool ## Run govulncheck to scan for known, reachable vulnerabilities (incl. stdlib/toolchain). + "$(GOVULNCHECK)" ./... + +GOLANGCI_LINT_VERSION ?= v2.12.2 +GOVULNCHECK_VERSION ?= v1.5.0 + +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + @test -f .custom-gcl.yml && { \ + echo "Building custom golangci-lint with plugins..." && \ + $(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \ + mv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \ + } || true + +.PHONY: govulncheck-tool +govulncheck-tool: $(GOVULNCHECK) ## Download govulncheck locally if necessary. +$(GOVULNCHECK): $(LOCALBIN) + $(call go-install-tool,$(GOVULNCHECK),golang.org/x/vuln/cmd/govulncheck,$(GOVULNCHECK_VERSION)) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)-$(HOST_PLATFORM)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)-$(HOST_PLATFORM)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package} ($(HOST_PLATFORM))" ;\ +rm -f "$(1)" ;\ +GOBIN="$(LOCALBIN)" go install $${package} ;\ +mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)-$(HOST_PLATFORM)" ;\ +} ;\ +ln -sf "$$(realpath "$(1)-$(3)-$(HOST_PLATFORM)")" "$(1)" +endef + +define gomodver +$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null) +endef diff --git a/cloudscale.go b/cloudscale.go index 920b71e5..2afb66b0 100644 --- a/cloudscale.go +++ b/cloudscale.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -139,7 +138,7 @@ func NewClient(httpClient *http.Client) *Client { return c } -func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) { +func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body any) (*http.Request, error) { rel, err := url.Parse(urlStr) if err != nil { return nil, err @@ -155,7 +154,7 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int } } - req, err := http.NewRequest(method, u.String(), buf) + req, err := http.NewRequestWithContext(ctx, method, u.String(), buf) if err != nil { return nil, err } @@ -171,8 +170,7 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int return req, nil } -func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) error { - +func (c *Client) Do(ctx context.Context, req *http.Request, v any) error { req = req.WithContext(ctx) resp, err := c.client.Do(req) @@ -213,7 +211,7 @@ func CheckResponse(r *http.Response) error { return nil } - data, err := ioutil.ReadAll(r.Body) + data, err := io.ReadAll(r.Body) res := map[string]string{} if err == nil && len(data) > 0 { err := json.Unmarshal(data, &res) diff --git a/cloudscale_test.go b/cloudscale_test.go index bc60eb35..75b28c47 100644 --- a/cloudscale_test.go +++ b/cloudscale_test.go @@ -3,7 +3,7 @@ package cloudscale import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" @@ -40,6 +40,7 @@ func teardown() { } func testHTTPMethod(t *testing.T, r *http.Request, expected string) { + t.Helper() if expected != r.Method { t.Errorf("Request method = %v, expected %v", r.Method, expected) } @@ -55,7 +56,6 @@ func TestNewClient(t *testing.T) { if c.UserAgent != userAgent { t.Errorf("NewClient UserAgent = %v, expected %v", c.UserAgent, userAgent) } - } func TestNewRequest(t *testing.T) { @@ -73,7 +73,7 @@ func TestNewRequest(t *testing.T) { } // test body was JSON encoded - body, _ := ioutil.ReadAll(req.Body) + body, _ := io.ReadAll(req.Body) if string(body) != outBody { t.Errorf("NewRequest(%v)Body = %v, expected %v", inBody, string(body), outBody) } @@ -115,7 +115,7 @@ func TestCheckResponse(t *testing.T) { res := &http.Response{ Request: &http.Request{}, StatusCode: http.StatusBadRequest, - Body: ioutil.NopCloser(strings.NewReader(`{"name": "This field may not be blank."}`)), + Body: io.NopCloser(strings.NewReader(`{"name": "This field may not be blank."}`)), } err := CheckResponse(res).(*ErrorResponse) @@ -146,7 +146,7 @@ func TestDo(t *testing.T) { if m := http.MethodGet; m != r.Method { t.Errorf("Request method = %v, expected %v", r.Method, m) } - fmt.Fprint(w, `{"A":"a"}`) + _, _ = fmt.Fprint(w, `{"A":"a"}`) }) req, _ := client.NewRequest(ctx, http.MethodGet, "/", nil) @@ -167,7 +167,7 @@ func TestDo_httpError(t *testing.T) { defer teardown() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "Bad Request", 400) + http.Error(w, "Bad Request", http.StatusBadRequest) }) req, _ := client.NewRequest(ctx, http.MethodGet, "/", nil) @@ -200,7 +200,7 @@ func TestDo_redirectLoop(t *testing.T) { } // TODO: Maybe add an argument with a description for the assertion. -func assertEqual(t *testing.T, expected interface{}, actual interface{}) { +func assertEqual(t *testing.T, expected any, actual any) { t.Helper() if !reflect.DeepEqual(expected, actual) { diff --git a/ctxutil_test.go b/ctxutil_test.go index 54058d4f..071d7a89 100644 --- a/ctxutil_test.go +++ b/ctxutil_test.go @@ -18,8 +18,11 @@ func TestOperationPath(t *testing.T) { t.Fatalf("expected %q, got %q", "v1/servers/:id", got) } + type nestedKey struct{} + nested := nestedKey{} + // Nested context should inherit. - child := context.WithValue(ctx, struct{}{}, "other") + child := context.WithValue(ctx, nested, "other") if got := OperationPath(child); got != "v1/servers/:id" { t.Fatalf("expected %q, got %q", "v1/servers/:id", got) } diff --git a/custom_image_imports_test.go b/custom_image_imports_test.go index c880040e..69ef0e39 100644 --- a/custom_image_imports_test.go +++ b/custom_image_imports_test.go @@ -24,15 +24,15 @@ func TestCustomImageImport_Create(t *testing.T) { } mux.HandleFunc("/v1/custom-images/import", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "name": "Test Image", - "tags": map[string]interface{}{ + "tags": map[string]any{ "tag": "one", "other": "tag", }, } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -54,7 +54,7 @@ func TestCustomImageImport_Create(t *testing.T) { "error_message": "", "tags": {} }` - io.WriteString(w, jsonStr) + _, _ = io.WriteString(w, jsonStr) }) customImageImport, err := client.CustomImageImports.Create(ctx, customImageImportRequest) @@ -75,7 +75,7 @@ func TestCustomImageImport_Get(t *testing.T) { mux.HandleFunc("/v1/custom-images/import/6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{ + _, _ = fmt.Fprint(w, `{ "href": "https://api.cloudscale.ch/v1/custom-images/import/11111111-1864-4608-853a-0771b6885a3a", "uuid": "11111111-1864-4608-853a-0771b6885a3a", "custom_image": { @@ -120,7 +120,7 @@ func TestCustomImageImport_List(t *testing.T) { mux.HandleFunc("/v1/custom-images/import", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{ + _, _ = fmt.Fprint(w, `[{ "href": "https://api.cloudscale.ch/v1/custom-images/import/11111111-1864-4608-853a-0771b6885a3a", "uuid": "11111111-1864-4608-853a-0771b6885a3a", "custom_image": { @@ -159,5 +159,4 @@ func TestCustomImageImport_List(t *testing.T) { if !reflect.DeepEqual(objectUsers, expected) { t.Errorf("CustomImageImport.List\n got=%#v\nwant=%#v", objectUsers, expected) } - } diff --git a/custom_images.go b/custom_images.go index be32e69a..f57eb970 100644 --- a/custom_images.go +++ b/custom_images.go @@ -43,10 +43,6 @@ type CustomImageService interface { GenericWaitForService[CustomImage] } -type CustomImageServiceOperations struct { - client *Client -} - var ImportIsSuccessful = func(importInfo *CustomImageImport) (bool, error) { if importInfo.Status == "success" { return true, nil diff --git a/custom_images_test.go b/custom_images_test.go index 311ada61..c8c16d9c 100644 --- a/custom_images_test.go +++ b/custom_images_test.go @@ -15,7 +15,7 @@ func TestCustomImage_Get(t *testing.T) { mux.HandleFunc("/v1/custom-images/11111111-1864-4608-853a-0771b6885a3a", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"uuid": "11111111-1864-4608-853a-0771b6885a3a", "created_at": "2019-05-27T16:45:32.241824Z"}`) + _, _ = fmt.Fprint(w, `{"uuid": "11111111-1864-4608-853a-0771b6885a3a", "created_at": "2019-05-27T16:45:32.241824Z"}`) }) objectUser, err := client.CustomImages.Get(ctx, "11111111-1864-4608-853a-0771b6885a3a") @@ -49,7 +49,7 @@ func TestCustomImage_List(t *testing.T) { mux.HandleFunc("/v1/custom-images", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"uuid": "11111111-1864-4608-853a-0771b6885a3a"}]`) + _, _ = fmt.Fprint(w, `[{"uuid": "11111111-1864-4608-853a-0771b6885a3a"}]`) }) customImages, err := client.CustomImages.List(ctx) @@ -61,7 +61,6 @@ func TestCustomImage_List(t *testing.T) { if !reflect.DeepEqual(customImages, expected) { t.Errorf("CustomImage.List\n got=%#v\nwant=%#v", customImages, expected) } - } func TestCustomImage_Update(t *testing.T) { diff --git a/flavors_test.go b/flavors_test.go index 770fea86..b79d040e 100644 --- a/flavors_test.go +++ b/flavors_test.go @@ -52,7 +52,7 @@ func TestFlavors_List(t *testing.T) { mux.HandleFunc("/v1/flavors", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, flavorsResponse) + _, _ = fmt.Fprint(w, flavorsResponse) }) flavors, err := client.Flavors.List(ctx) @@ -101,5 +101,4 @@ func TestFlavors_List(t *testing.T) { want, _ := json.MarshalIndent(expected, "", " ") t.Errorf("Flavors.List\n got=%s\nwant=%s", got, want) } - } diff --git a/floating_ips_test.go b/floating_ips_test.go index e8a958af..d88102d2 100644 --- a/floating_ips_test.go +++ b/floating_ips_test.go @@ -17,7 +17,6 @@ func TestFloatingIPs_IP(t *testing.T) { } if ip := floatingIP.IP(); ip != expected { t.Errorf("FloatingIP.IP got=%s\nwant=%s", ip, expected) - } } @@ -31,12 +30,12 @@ func TestFloatingIPs_Create(t *testing.T) { } mux.HandleFunc("/v1/floating-ips", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "ip_version": float64(6), "server": "47cec963-fcd2-482f-bdb6-24461b2d47b1", } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -46,7 +45,7 @@ func TestFloatingIPs_Create(t *testing.T) { t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) } - fmt.Fprintf(w, `{"network": "2001:db8::cafe/128"}`) + _, _ = fmt.Fprintf(w, `{"network": "2001:db8::cafe/128"}`) }) floatingIP, err := client.FloatingIPs.Create(ctx, floatingIPrequest) @@ -57,7 +56,6 @@ func TestFloatingIPs_Create(t *testing.T) { if network := floatingIP.Network; network != "2001:db8::cafe/128" { t.Errorf("expected network '%s', received '%s'", floatingIP.Network, network) } - } func TestFloatingIPs_Get(t *testing.T) { @@ -66,7 +64,7 @@ func TestFloatingIPs_Get(t *testing.T) { mux.HandleFunc("/v1/floating-ips/192.0.2.123", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"network": "192.0.2.123/32", "created_at": "2019-05-27T16:45:32.241824Z"}`) + _, _ = fmt.Fprint(w, `{"network": "192.0.2.123/32", "created_at": "2019-05-27T16:45:32.241824Z"}`) }) floatingIP, err := client.FloatingIPs.Get(ctx, "192.0.2.123") @@ -89,11 +87,11 @@ func TestFloatingIPs_Update(t *testing.T) { } mux.HandleFunc("/v1/floating-ips/192.0.2.123", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "server": "47777777-fcd2-482f-bdb6-24461b2d47b1", } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -130,7 +128,7 @@ func TestFloatingIPs_List(t *testing.T) { mux.HandleFunc("/v1/floating-ips", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"network": "192.0.2.123/32"}]`) + _, _ = fmt.Fprint(w, `[{"network": "192.0.2.123/32"}]`) }) servers, err := client.FloatingIPs.List(ctx) @@ -142,5 +140,4 @@ func TestFloatingIPs_List(t *testing.T) { if !reflect.DeepEqual(servers, expected) { t.Errorf("FloatingIPs.List\n got=%#v\nwant=%#v", servers, expected) } - } diff --git a/generic_service.go b/generic_service.go index 1ae1d21f..f00c15cd 100644 --- a/generic_service.go +++ b/generic_service.go @@ -3,9 +3,10 @@ package cloudscale import ( "context" "fmt" - "github.com/cenkalti/backoff/v5" "net/http" "time" + + "github.com/cenkalti/backoff/v5" ) type GenericCreateService[TResource any, TCreateRequest any] interface { diff --git a/go.mod b/go.mod index 668bdff6..c9de184c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cloudscale-ch/cloudscale-go-sdk/v9 -go 1.25.0 +go 1.26.4 require ( github.com/cenkalti/backoff/v5 v5.0.3 diff --git a/instrumentation/instrumentation_test.go b/instrumentation/instrumentation_test.go index 168e70a1..028fc941 100644 --- a/instrumentation/instrumentation_test.go +++ b/instrumentation/instrumentation_test.go @@ -46,7 +46,7 @@ func newInstrumentedClient(reg prometheus.Registerer, tracer trace.Tracer) *http func doRequest(t *testing.T, c *http.Client, method, url, opPath string) (*http.Response, error) { t.Helper() - req, err := http.NewRequest(method, url, nil) + req, err := http.NewRequestWithContext(t.Context(), method, url, nil) if err != nil { t.Fatal(err) } @@ -94,13 +94,13 @@ func newRecordingTracer(t *testing.T) (trace.Tracer, *tracetest.SpanRecorder) { return tp.Tracer("test"), sr } -func mustDoRequest(t *testing.T, c *http.Client, method, url, opPath string) { +func mustDoRequest(t *testing.T, c *http.Client, url, opPath string) { t.Helper() - resp, err := doRequest(t, c, method, url, opPath) + resp, err := doRequest(t, c, http.MethodGet, url, opPath) if err != nil { t.Fatal(err) } - resp.Body.Close() + _ = resp.Body.Close() } func singleSpan(t *testing.T, sr *tracetest.SpanRecorder) sdktrace.ReadOnlySpan { @@ -174,7 +174,7 @@ func TestInstrumentedTransport_StatusLabels(t *testing.T) { if err != nil { t.Fatal(err) } - resp.Body.Close() + _ = resp.Body.Close() } wantEndpoint := tc.opPath @@ -235,7 +235,7 @@ func TestInstrumentedTransport_Concurrent(t *testing.T) { var wg sync.WaitGroup wg.Add(n) - for i := 0; i < n; i++ { + for range n { go func() { defer wg.Done() resp, err := doRequest(t, client, http.MethodGet, server.URL+"/v1/servers", "v1/servers") @@ -243,11 +243,11 @@ func TestInstrumentedTransport_Concurrent(t *testing.T) { t.Errorf("request failed: %v", err) return } - resp.Body.Close() + _ = resp.Body.Close() }() } - for i := 0; i < n; i++ { + for range n { <-arrived } @@ -288,7 +288,7 @@ func TestInstrumentedTransport_MetricsOnly(t *testing.T) { server := newTestServer(t, statusHandler(http.StatusTeapot)) client := newInstrumentedClient(reg, nil) - mustDoRequest(t, client, http.MethodGet, server.URL+"/v1/flavors", "v1/flavors") + mustDoRequest(t, client, server.URL+"/v1/flavors", "v1/flavors") for _, name := range []string{ "cloudscale_requests_total", @@ -310,7 +310,7 @@ func TestInstrumentedTransport_DefaultSubsystem(t *testing.T) { }), } - mustDoRequest(t, client, http.MethodGet, server.URL+"/v1/flavors", "v1/flavors") + mustDoRequest(t, client, server.URL+"/v1/flavors", "v1/flavors") metricFamily(t, reg, "cloudscale_requests_total") } @@ -322,7 +322,7 @@ func TestInstrumentedTransport_SharedRegistry(t *testing.T) { // Two independent transports sharing the same registry must reuse the same // underlying collectors — both increments should land on a single series. for _, c := range []*http.Client{newInstrumentedClient(reg, nil), newInstrumentedClient(reg, nil)} { - mustDoRequest(t, c, http.MethodGet, server.URL+"/v1/servers", "v1/servers") + mustDoRequest(t, c, server.URL+"/v1/servers", "v1/servers") } total := metricFamily(t, reg, "cloudscale_requests_total") @@ -341,7 +341,7 @@ func TestInstrumentedTransport_CombinedMetricsAndTracing(t *testing.T) { server := newTestServer(t, statusHandler(http.StatusOK)) client := newInstrumentedClient(reg, tracer) - mustDoRequest(t, client, http.MethodGet, server.URL+"/v1/servers/123", "v1/servers/:id") + mustDoRequest(t, client, server.URL+"/v1/servers/123", "v1/servers/:id") total := metricFamily(t, reg, "cloudscale_requests_total") labels := labelsOf(total.Metric[0]) @@ -379,7 +379,7 @@ func TestInstrumentedTransport_SpanAttributes(t *testing.T) { client := newInstrumentedClient(nil, tracer) reqURL := server.URL + "/some/path?bucket_name=secret&objects_user_id=user-uuid" - mustDoRequest(t, client, http.MethodGet, reqURL, tc.opPath) + mustDoRequest(t, client, reqURL, tc.opPath) span := singleSpan(t, sr) @@ -425,7 +425,7 @@ func TestInstrumentedTransport_URLFullRedactsQuery(t *testing.T) { client := newInstrumentedClient(nil, tracer) reqURL := server.URL + "/v1/metrics/buckets?start=2026-01-01&end=2026-01-02&bucket_name=secret&objects_user_id=user-uuid" - mustDoRequest(t, client, http.MethodGet, reqURL, "v1/metrics/buckets") + mustDoRequest(t, client, reqURL, "v1/metrics/buckets") got := attrsOf(singleSpan(t, sr))["url.full"].AsString() want := server.URL + "/v1/metrics/buckets" @@ -449,7 +449,7 @@ func TestInstrumentedTransport_Propagation(t *testing.T) { client := newInstrumentedClient(nil, tracer) - mustDoRequest(t, client, http.MethodGet, server.URL+"/v1/servers", "v1/servers") + mustDoRequest(t, client, server.URL+"/v1/servers", "v1/servers") if got == "" { t.Fatal("expected traceparent header to be injected") diff --git a/instrumentation/metrics.go b/instrumentation/metrics.go index b0dd110b..da87acc0 100644 --- a/instrumentation/metrics.go +++ b/instrumentation/metrics.go @@ -61,7 +61,7 @@ func registerOrReuseGauge(reg prometheus.Registerer, opts prometheus.GaugeOpts) } // NewMetricsCollector creates a metricsCollector backed by the given registry. -func NewMetricsCollector(reg prometheus.Registerer, subsystem string) *metricsCollector { +func NewMetricsCollector(reg prometheus.Registerer, subsystem string) *metricsCollector { //revive:disable:unexported-return return &metricsCollector{ requestsTotal: registerOrReuseCounterVec(reg, prometheus.CounterOpts{ Subsystem: subsystem, diff --git a/load_balancer_health_monitors.go b/load_balancer_health_monitors.go index 84e5de83..e4070312 100644 --- a/load_balancer_health_monitors.go +++ b/load_balancer_health_monitors.go @@ -26,7 +26,7 @@ type LoadBalancerHealthMonitor struct { type LoadBalancerHealthMonitorHTTP struct { ExpectedCodes []string `json:"expected_codes,omitempty"` Method string `json:"method,omitempty"` - UrlPath string `json:"url_path,omitempty"` + URLPath string `json:"url_path,omitempty"` Version string `json:"version,omitempty"` Host *string `json:"host,omitempty"` } @@ -45,7 +45,7 @@ type LoadBalancerHealthMonitorRequest struct { type LoadBalancerHealthMonitorHTTPRequest struct { ExpectedCodes []string `json:"expected_codes,omitempty"` Method string `json:"method,omitempty"` - UrlPath string `json:"url_path,omitempty"` + URLPath string `json:"url_path,omitempty"` Version string `json:"version,omitempty"` Host *string `json:"host,omitempty"` } diff --git a/load_balancer_listeners_test.go b/load_balancer_listeners_test.go index eebf78dc..62d70f42 100644 --- a/load_balancer_listeners_test.go +++ b/load_balancer_listeners_test.go @@ -14,7 +14,7 @@ func TestIntegrationLoadBalancerListener_GetWithPool(t *testing.T) { mux.HandleFunc("/v1/load-balancers/listeners/754c3797-57de-4fd2-a5c9-97efa2a0c466", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{ + _, _ = fmt.Fprint(w, `{ "href": "https://lab-api.cloudscale.ch/v1/load-balancers/listeners/754c3797-57de-4fd2-a5c9-97efa2a0c466", "uuid": "754c3797-57de-4fd2-a5c9-97efa2a0c466", "name": "web-lb1-listener", @@ -82,7 +82,7 @@ func TestIntegrationLoadBalancerListener_GetWithoutPool(t *testing.T) { mux.HandleFunc("/v1/load-balancers/listeners/3d6ca1f4-5aea-41f5-b724-0f3054b60e85", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{ + _, _ = fmt.Fprint(w, `{ "href": "https://lab-api.cloudscale.ch/v1/load-balancers/listeners/3d6ca1f4-5aea-41f5-b724-0f3054b60e85", "uuid": "3d6ca1f4-5aea-41f5-b724-0f3054b60e85", "name": "web-lb1-listener-without-pool", diff --git a/metadata.go b/metadata.go index 36c73c7c..96b68628 100644 --- a/metadata.go +++ b/metadata.go @@ -1,24 +1,25 @@ -// Package metadata implements a client for the cloudscale.ch's OpenStack -// metadata API. This API allows a server to inspect information about itself, -// like its server ID. -// -// Documentation for the API is available at: -// -// https://www.cloudscale.ch/en/api/v1 package cloudscale import ( + "context" "encoding/json" "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "path" "time" ) +// metadata implements a client for the cloudscale.ch's OpenStack +// metadata API. This API allows a server to inspect information about itself, +// like its server ID. +// +// Documentation for the API is available at: +// +// https://www.cloudscale.ch/en/api/v1 + const ( maxErrMsgLen = 128 // arbitrary max length for error messages @@ -36,70 +37,76 @@ var ( }() ) -// Client to interact with cloudscale.ch's OpenStack metadata API, from inside +// MetadataClient to interact with cloudscale.ch's OpenStack metadata API, from inside // a server. type MetadataClient struct { client *http.Client BaseURL *url.URL } -// NewClient creates a client for the metadata API. +// NewMetadataClient creates a client for the metadata API. func NewMetadataClient(httpClient *http.Client) *MetadataClient { if httpClient == nil { - httpClient = http.DefaultClient + httpClient = &http.Client{Timeout: defaultTimeout} } client := &MetadataClient{ - client: &http.Client{Timeout: defaultTimeout}, + client: httpClient, BaseURL: defaultMetadataBaseURL, } return client } -// Metadata contains the entire contents of a OpenStack's metadata. +// GetMetadata contains the entire contents of a OpenStack's metadata. // This method is unique because it returns all of the // metadata at once, instead of individual metadata items. -func (c *MetadataClient) GetMetadata() (*Metadata, error) { +func (c *MetadataClient) GetMetadata(ctx context.Context) (*Metadata, error) { metadata := new(Metadata) - err := c.getResource("meta_data.json", func(r io.Reader) error { + err := c.getResource(ctx, "meta_data.json", func(r io.Reader) error { return json.NewDecoder(r).Decode(metadata) }) return metadata, err } -// ServerID returns the Server's unique identifier. This is +// GetServerID returns the Server's unique identifier. This is // automatically generated upon Server creation. -func (c *MetadataClient) GetServerID() (string, error) { - metadata, err := c.GetMetadata() +func (c *MetadataClient) GetServerID(ctx context.Context) (string, error) { + metadata, err := c.GetMetadata(ctx) if err != nil { return "", err } if metadata.Meta.CloudscaleUUID == "" { - return "", errors.New("The CloudscaleUUID was not defined in metadata") + return "", errors.New("CloudscaleUUID not defined in metadata") } return metadata.Meta.CloudscaleUUID, nil } -// RawUserData returns the user data that was provided by the user +// GetRawUserData returns the user data that was provided by the user // during Server creation. User data for cloudscale.ch is a YAML // Script that is used for cloud-init. -func (c *MetadataClient) GetRawUserData() (string, error) { +func (c *MetadataClient) GetRawUserData(ctx context.Context) (string, error) { var userdata string - err := c.getResource("user_data", func(r io.Reader) error { - userdataraw, err := ioutil.ReadAll(r) + err := c.getResource(ctx, "user_data", func(r io.Reader) error { + userdataraw, err := io.ReadAll(r) userdata = string(userdataraw) return err }) return userdata, err } -func (c *MetadataClient) getResource(resource string, decoder func(r io.Reader) error) error { +func (c *MetadataClient) getResource(ctx context.Context, resource string, decoder func(r io.Reader) error) error { url := c.resolve(defaultPath, resource) - resp, err := c.client.Get(url) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + resp, err := c.client.Do(req) if err != nil { return err } - defer resp.Body.Close() + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) if resp.StatusCode != http.StatusOK { return c.makeError(resp) } @@ -107,7 +114,7 @@ func (c *MetadataClient) getResource(resource string, decoder func(r io.Reader) } func (c *MetadataClient) makeError(resp *http.Response) error { - body, _ := ioutil.ReadAll(io.LimitReader(resp.Body, maxErrMsgLen)) + body, _ := io.ReadAll(io.LimitReader(resp.Body, maxErrMsgLen)) if len(body) >= maxErrMsgLen { body = append(body[:maxErrMsgLen], []byte("... (elided)")...) } else if len(body) == 0 { diff --git a/metadata_json.go b/metadata_json.go index 7f4788cb..70fa8196 100644 --- a/metadata_json.go +++ b/metadata_json.go @@ -4,8 +4,8 @@ type Metadata struct { AvailabilityZone string `json:"availability_zone,omitempty"` // For now don't define those, because they don't need to match the // official cloudscale.ch API and are part of OpenStack. - //Hostname string `json:"hostname,omitempty"` - //Name string `json:"user_data,omitempty"` + // Hostname string `json:"hostname,omitempty"` + // Name string `json:"user_data,omitempty"` Meta struct { CloudscaleUUID string `json:"cloudscale_uuid,omitempty"` diff --git a/metadata_test.go b/metadata_test.go index ecb66d3f..cc47ba55 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -12,10 +12,10 @@ func TestServerId(t *testing.T) { mux.HandleFunc("/openstack/2017-02-22/meta_data.json", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprintf(w, `{"meta": {"cloudscale_uuid": "foobar"}}`) + _, _ = fmt.Fprintf(w, `{"meta": {"cloudscale_uuid": "foobar"}}`) }) - serverID, err := metadataClient.GetServerID() + serverID, err := metadataClient.GetServerID(t.Context()) if err != nil { t.Errorf("GetServerID returned error: %v", err) } @@ -23,7 +23,6 @@ func TestServerId(t *testing.T) { if serverID != "foobar" { t.Errorf("expected id 'foobar', received '%s'", serverID) } - } func TestRawUserData(t *testing.T) { @@ -32,10 +31,10 @@ func TestRawUserData(t *testing.T) { mux.HandleFunc("/openstack/2017-02-22/user_data", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `abcdef`) + _, _ = fmt.Fprint(w, `abcdef`) }) - userData, err := metadataClient.GetRawUserData() + userData, err := metadataClient.GetRawUserData(t.Context()) if err != nil { t.Errorf("Server.Get returned error: %v", err) } diff --git a/metrics_test.go b/metrics_test.go index b154edbe..f299aa6c 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -55,7 +55,7 @@ func TestMetrics_GetBucketMetrics(t *testing.T) { } ] }` - io.WriteString(w, jsonStr) + _, _ = io.WriteString(w, jsonStr) }) metrics, err := client.Metrics.GetBucketMetrics(ctx, metricsRequest) @@ -118,7 +118,7 @@ func TestMetrics_GetBucketMetricsAdditionalArgs(t *testing.T) { assertEqual(t, url.Values(expected), r.URL.Query()) // Dummy response. - fmt.Fprintf(w, "{}") + _, _ = fmt.Fprintf(w, "{}") }) _, err := client.Metrics.GetBucketMetrics(ctx, metricsRequest) diff --git a/networks.go b/networks.go index 816539a6..587e8d41 100644 --- a/networks.go +++ b/networks.go @@ -48,7 +48,3 @@ type NetworkService interface { GenericDeleteService[Network] GenericWaitForService[Network] } - -type NetworkServiceOperations struct { - client *Client -} diff --git a/networks_test.go b/networks_test.go index 48635363..c9788733 100644 --- a/networks_test.go +++ b/networks_test.go @@ -18,11 +18,11 @@ func TestNetworks_Create(t *testing.T) { } mux.HandleFunc("/v1/networks", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "name": "netzli", } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -32,7 +32,7 @@ func TestNetworks_Create(t *testing.T) { t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) } - fmt.Fprintf(w, `{"uuid": "42cec963-fcd2-482f-bdb6-24461b2d47b1"}`) + _, _ = fmt.Fprintf(w, `{"uuid": "42cec963-fcd2-482f-bdb6-24461b2d47b1"}`) }) network, err := client.Networks.Create(ctx, networkRequest) @@ -43,7 +43,6 @@ func TestNetworks_Create(t *testing.T) { if id := network.UUID; id != "42cec963-fcd2-482f-bdb6-24461b2d47b1" { t.Errorf("expected id '%s', received '%s'", network.UUID, id) } - } func TestNetworks_Get(t *testing.T) { @@ -52,7 +51,7 @@ func TestNetworks_Get(t *testing.T) { mux.HandleFunc("/v1/networks/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", "created_at": "2019-05-27T16:45:32.241824Z"}`) + _, _ = fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", "created_at": "2019-05-27T16:45:32.241824Z"}`) }) network, err := client.Networks.Get(ctx, "cfde831a-4e87-4a75-960f-89b0148aa2cc") @@ -86,7 +85,7 @@ func TestNetworks_List(t *testing.T) { mux.HandleFunc("/v1/networks", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) + _, _ = fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) }) networks, err := client.Networks.List(ctx) @@ -98,5 +97,4 @@ func TestNetworks_List(t *testing.T) { if !reflect.DeepEqual(networks, expected) { t.Errorf("Networks.List\n got=%#v\nwant=%#v", networks, expected) } - } diff --git a/objects_users_test.go b/objects_users_test.go index 92b116b7..02a72448 100644 --- a/objects_users_test.go +++ b/objects_users_test.go @@ -25,15 +25,15 @@ func TestObjectsUser_Create(t *testing.T) { } mux.HandleFunc("/v1/objects-users", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "display_name": "TestBucket", - "tags": map[string]interface{}{ + "tags": map[string]any{ "tag": "one", "other": "tag", }, } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -42,6 +42,7 @@ func TestObjectsUser_Create(t *testing.T) { if !reflect.DeepEqual(v, expected) { t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) } + //gosec:disable G101 - not a real credential jsonStr := `{ "href": "https://api.cloudscale.ch/v1/objects-users/6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15", "id": "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15", @@ -55,7 +56,7 @@ func TestObjectsUser_Create(t *testing.T) { "other": "tag" } }` - io.WriteString(w, jsonStr) + _, _ = io.WriteString(w, jsonStr) }) objectsUser, err := client.ObjectsUsers.Create(ctx, objectsUserRequest) @@ -75,7 +76,7 @@ func TestObjectsUser_Get(t *testing.T) { mux.HandleFunc("/v1/objects-users/6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"id": "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15"}`) + _, _ = fmt.Fprint(w, `{"id": "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15"}`) }) objectUser, err := client.ObjectsUsers.Get(ctx, "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15") @@ -109,7 +110,7 @@ func TestObjectsUser_List(t *testing.T) { mux.HandleFunc("/v1/objects-users", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"id": "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15"}]`) + _, _ = fmt.Fprint(w, `[{"id": "6fe39134bf4178747eebc429f82cfafdd08891d4279d0d899bc4012db1db6a15"}]`) }) objectUsers, err := client.ObjectsUsers.List(ctx) @@ -121,7 +122,6 @@ func TestObjectsUser_List(t *testing.T) { if !reflect.DeepEqual(objectUsers, expected) { t.Errorf("ObjectsUser.List\n got=%#v\nwant=%#v", objectUsers, expected) } - } func TestObjectsUser_Update(t *testing.T) { diff --git a/regions_test.go b/regions_test.go index 25f15544..d763a0b1 100644 --- a/regions_test.go +++ b/regions_test.go @@ -28,7 +28,7 @@ func TestRegions_List(t *testing.T) { mux.HandleFunc("/v1/regions", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, regionsResponse) + _, _ = fmt.Fprint(w, regionsResponse) }) regions, err := client.Regions.List(ctx) @@ -51,5 +51,4 @@ func TestRegions_List(t *testing.T) { if !reflect.DeepEqual(regions, expected) { t.Errorf("Regions.List\n got=%#v\nwant=%#v", regions, expected) } - } diff --git a/server_groups_test.go b/server_groups_test.go index 3377a9ff..c930cef9 100644 --- a/server_groups_test.go +++ b/server_groups_test.go @@ -18,12 +18,12 @@ func TestServersGroups_Create(t *testing.T) { } mux.HandleFunc("/v1/server-groups", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "name": "db-servers", "type": "anti-affinity", } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -33,7 +33,7 @@ func TestServersGroups_Create(t *testing.T) { t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) } - fmt.Fprintf(w, `{"uuid": "42cec963-fcd2-482f-bdb6-24461b2d47b1"}`) + _, _ = fmt.Fprintf(w, `{"uuid": "42cec963-fcd2-482f-bdb6-24461b2d47b1"}`) }) serverGroup, err := client.ServerGroups.Create(ctx, serverGroupRequest) @@ -44,7 +44,6 @@ func TestServersGroups_Create(t *testing.T) { if id := serverGroup.UUID; id != "42cec963-fcd2-482f-bdb6-24461b2d47b1" { t.Errorf("expected id '%s', received '%s'", serverGroup.UUID, id) } - } func TestServerGroups_Get(t *testing.T) { @@ -53,7 +52,7 @@ func TestServerGroups_Get(t *testing.T) { mux.HandleFunc("/v1/server-groups/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc"}`) + _, _ = fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc"}`) }) serverGroup, err := client.ServerGroups.Get(ctx, "cfde831a-4e87-4a75-960f-89b0148aa2cc") @@ -87,7 +86,7 @@ func TestServerGroups_List(t *testing.T) { mux.HandleFunc("/v1/server-groups", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) + _, _ = fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) }) serverGroups, err := client.ServerGroups.List(ctx) @@ -99,5 +98,4 @@ func TestServerGroups_List(t *testing.T) { if !reflect.DeepEqual(serverGroups, expected) { t.Errorf("ServerGroups.List\n got=%#v\nwant=%#v", serverGroups, expected) } - } diff --git a/servers.go b/servers.go index 0e6b5a59..ee747afb 100644 --- a/servers.go +++ b/servers.go @@ -182,7 +182,7 @@ func (s ServerServiceOperations) Update(ctx context.Context, id string, req *Ser case ServerRebooted: err = s.Reboot(ctx, id) default: - return fmt.Errorf("Status Not Supported %s", req.Status) + return fmt.Errorf("unsupported status %q", req.Status) } if err != nil { return err diff --git a/servers_test.go b/servers_test.go index 54395f73..d2396ffc 100644 --- a/servers_test.go +++ b/servers_test.go @@ -23,15 +23,15 @@ func TestServers_Create(t *testing.T) { } mux.HandleFunc("/v1/servers", func(w http.ResponseWriter, r *http.Request) { - expected := map[string]interface{}{ + expected := map[string]any{ "name": "mysql", "flavor": "flex-4", "image": "debian", "volume_size_gb": float64(50), - "ssh_keys": []interface{}{"key"}, + "ssh_keys": []any{"key"}, } - var v map[string]interface{} + var v map[string]any err := json.NewDecoder(r.Body).Decode(&v) if err != nil { t.Fatalf("decode json: %v", err) @@ -41,7 +41,7 @@ func TestServers_Create(t *testing.T) { t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) } - fmt.Fprintf(w, `{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}`) + _, _ = fmt.Fprintf(w, `{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}`) }) server, err := client.Servers.Create(ctx, serverRequest) @@ -52,7 +52,6 @@ func TestServers_Create(t *testing.T) { if id := server.UUID; id != "47cec963-fcd2-482f-bdb6-24461b2d47b1" { t.Errorf("expected id '%s', received '%s'", server.UUID, id) } - } func TestServers_Get(t *testing.T) { @@ -61,7 +60,7 @@ func TestServers_Get(t *testing.T) { mux.HandleFunc("/v1/servers/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", "created_at": "2019-05-27T16:45:32.241824Z"}`) + _, _ = fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc", "created_at": "2019-05-27T16:45:32.241824Z"}`) }) server, err := client.Servers.Get(ctx, "cfde831a-4e87-4a75-960f-89b0148aa2cc") @@ -95,7 +94,7 @@ func TestServers_List(t *testing.T) { mux.HandleFunc("/v1/servers", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) + _, _ = fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) }) servers, err := client.Servers.List(ctx) @@ -107,7 +106,6 @@ func TestServers_List(t *testing.T) { if !reflect.DeepEqual(servers, expected) { t.Errorf("Servers.List\n got=%#v\nwant=%#v", servers, expected) } - } func TestServers_Reboot(t *testing.T) { @@ -199,5 +197,4 @@ func TestServers_Update(t *testing.T) { if err == nil { t.Errorf("Servers.Update returned error: %v", err) } - } diff --git a/subnets.go b/subnets.go index 7ac57515..76ffd801 100644 --- a/subnets.go +++ b/subnets.go @@ -107,7 +107,3 @@ type SubnetService interface { GenericDeleteService[Subnet] GenericWaitForService[Subnet] } - -type SubnetServiceOperations struct { - client *Client -} diff --git a/subnets_test.go b/subnets_test.go index 41bf0545..6ac18419 100644 --- a/subnets_test.go +++ b/subnets_test.go @@ -14,7 +14,7 @@ func TestSubnets_Get(t *testing.T) { mux.HandleFunc("/v1/subnets/cfde831a-4e87-4a75-960f-89b0148aa2cc", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc"}`) + _, _ = fmt.Fprint(w, `{"uuid": "cfde831a-4e87-4a75-960f-89b0148aa2cc"}`) }) subnet, err := client.Subnets.Get(ctx, "cfde831a-4e87-4a75-960f-89b0148aa2cc") @@ -34,7 +34,7 @@ func TestSubnets_List(t *testing.T) { mux.HandleFunc("/v1/subnets", func(w http.ResponseWriter, r *http.Request) { testHTTPMethod(t, r, http.MethodGet) - fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) + _, _ = fmt.Fprint(w, `[{"uuid": "47cec963-fcd2-482f-bdb6-24461b2d47b1"}]`) }) subnets, err := client.Subnets.List(ctx) @@ -46,7 +46,6 @@ func TestSubnets_List(t *testing.T) { if !reflect.DeepEqual(subnets, expected) { t.Errorf("Subnets.List\n got=%#v\nwant=%#v", subnets, expected) } - } func TestMarshalingOfDNSServersInSubnetUpdateRequest(t *testing.T) { diff --git a/tags_test.go b/tags_test.go index 45d7bc0d..b2e6dd6a 100644 --- a/tags_test.go +++ b/tags_test.go @@ -19,7 +19,7 @@ func TestTagsToQueryString(t *testing.T) { for _, tt := range toQueryStringTestCases { t.Run(fmt.Sprintf("%#v", tt.tags), func(t *testing.T) { // arrange - req, _ := http.NewRequest("GET", "http://example.com", nil) + req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://example.com", nil) // act requestModifier := WithTagFilter(tt.tags) diff --git a/test/integration/cloudscale_test.go b/test/integration/cloudscale_test.go index 5e82a02a..8d3ea96d 100644 --- a/test/integration/cloudscale_test.go +++ b/test/integration/cloudscale_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -25,7 +24,7 @@ var ( func TestMain(m *testing.M) { // setup tests - testRunPrefix = fmt.Sprintf("go-sdk-%d", rand.Intn(100000)) + testRunPrefix = fmt.Sprintf("go-sdk-%d", rand.Intn(100000)) //gosec:disable G404 - random number not cryptographically relevant token := os.Getenv("CLOUDSCALE_API_TOKEN") if token == "" { diff --git a/test/integration/custom_images_integration_test.go b/test/integration/custom_images_integration_test.go index 45986d0e..0945358f 100644 --- a/test/integration/custom_images_integration_test.go +++ b/test/integration/custom_images_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -90,22 +89,26 @@ func TestIntegrationCustomImage_CRUD(t *testing.T) { for _, algo := range []string{"md5", "sha256"} { checksumURL := fmt.Sprintf("%s.%s", testImageURL, algo) - resp, err := http.Get(checksumURL) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, checksumURL, nil) + if err != nil { + t.Fatalf("NewRequestWithContext returned error %s\n", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } if resp.StatusCode != http.StatusOK { - t.Fatal(fmt.Sprintf("Wrong http status code\n got=%#v\nwant=%#v", resp.Status, http.StatusOK)) + t.Fatalf("Wrong http status code\n got=%#v\nwant=%#v", resp.Status, http.StatusOK) } - defer resp.Body.Close() body, err := io.ReadAll(resp.Body) + _ = resp.Body.Close() if err != nil { t.Fatal(err) } checksum := strings.TrimSpace(string(body)) if checksum != customImage.Checksums[algo] { - t.Error(fmt.Sprintf("Checksum does not match\n got=%#v\nwant=%#v", customImage.Checksums[algo], checksum)) + t.Errorf("Checksum does not match\n got=%#v\nwant=%#v", customImage.Checksums[algo], checksum) } } diff --git a/test/integration/flavors_integration_test.go b/test/integration/flavors_integration_test.go index 2f645be9..532446cb 100644 --- a/test/integration/flavors_integration_test.go +++ b/test/integration/flavors_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -21,7 +20,7 @@ func TestListFlavors(t *testing.T) { t.Fatalf("Flavors.List returned error %s\n", err) } - if len(allFlavors) <= 0 { + if len(allFlavors) == 0 { t.Fatal("Flavors.List returned empty slice\n", err) } diff --git a/test/integration/floating_ips_integration_test.go b/test/integration/floating_ips_integration_test.go index 02a96c9a..ff427788 100644 --- a/test/integration/floating_ips_integration_test.go +++ b/test/integration/floating_ips_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -107,7 +106,7 @@ func TestIntegrationFloatingIP_CRUD_LoadBalancer(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(loadBalancer.UUID, t) + waitUntilLB(t, loadBalancer.UUID) createFloatingIPRequest := &cloudscale.FloatingIPCreateRequest{ IPVersion: 4, @@ -283,6 +282,7 @@ func TestIntegrationFloatingIP_MultiSite(t *testing.T) { } func createFloatingIPInRegionAndAssert(t *testing.T, region cloudscale.Region) { + t.Helper() createServerRequest := &cloudscale.ServerRequest{ Name: testRunPrefix, @@ -386,7 +386,6 @@ func TestIntegrationFloatingIP_PrefixLength(t *testing.T) { if err != nil { t.Fatalf("FloatingIPs.Delete returned error %s\n", err) } - } func TestIntegrationFloatingIP_Global(t *testing.T) { diff --git a/test/integration/helper.go b/test/integration/helper.go index 631c2bda..f268b962 100644 --- a/test/integration/helper.go +++ b/test/integration/helper.go @@ -1,14 +1,14 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "math/rand" "reflect" "testing" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func getAllZones() ([]cloudscale.ZoneStub, error) { @@ -30,19 +30,16 @@ func getAllRegions() ([]cloudscale.Region, error) { func randomNotVerySecurePassword(length int) string { // based on: https://stackoverflow.com/a/12321192 + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" bytes := make([]byte, length) - for i := 0; i < length; i++ { - bytes[i] = byte(randInt(65, 90)) + for i := range length { + bytes[i] = letters[rand.Intn(len(letters))] //gosec:disable G404 - random number not cryptographically relevant } return string(bytes) } -func randInt(min int, max int) int { - return min + rand.Intn(max-min) -} - // TODO: Maybe add an argument with a description for the assertion. -func assertEqual(t *testing.T, expected interface{}, actual interface{}) { +func assertEqual(t *testing.T, expected any, actual any) { t.Helper() if !reflect.DeepEqual(expected, actual) { diff --git a/test/integration/load_balancer_health_monitors_integration_test.go b/test/integration/load_balancer_health_monitors_integration_test.go index df5c8842..1c2b607a 100644 --- a/test/integration/load_balancer_health_monitors_integration_test.go +++ b/test/integration/load_balancer_health_monitors_integration_test.go @@ -1,14 +1,14 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationLoadBalancerHealthMonitor_CRUD(t *testing.T) { @@ -19,7 +19,7 @@ func TestIntegrationLoadBalancerHealthMonitor_CRUD(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { @@ -95,7 +95,7 @@ func TestIntegrationLoadBalancerHealthMonitor_Update(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { @@ -162,7 +162,7 @@ func TestIntegrationLoadBalancerHealthMonitor_HTTP_Update(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { @@ -190,7 +190,7 @@ func TestIntegrationLoadBalancerHealthMonitor_HTTP_Update(t *testing.T) { expectedHTTP := cloudscale.LoadBalancerHealthMonitorHTTP{ ExpectedCodes: []string{"200"}, Method: "GET", - UrlPath: "/", + URLPath: "/", Version: "1.1", Host: &hostName, } @@ -220,13 +220,13 @@ func TestIntegrationLoadBalancerHealthMonitor_HTTP_Update(t *testing.T) { expectedUpdatedHTTP := cloudscale.LoadBalancerHealthMonitorHTTP{ ExpectedCodes: []string{"201", "200"}, Method: "GET", - UrlPath: "/", + URLPath: "/", Version: "1.1", Host: &hostName, } - updatedHttp := updated.HTTP - if !reflect.DeepEqual(updatedHttp, &expectedUpdatedHTTP) { - t.Errorf("updated.HTTP \n got=%#v\nwant=%#v", updatedHttp, &expectedUpdatedHTTP) + updatedHTTP := updated.HTTP + if !reflect.DeepEqual(updatedHTTP, &expectedUpdatedHTTP) { + t.Errorf("updated.HTTP \n got=%#v\nwant=%#v", updatedHTTP, &expectedUpdatedHTTP) } err = client.LoadBalancerHealthMonitors.Delete(context.Background(), updated.UUID) diff --git a/test/integration/load_balancer_listeners_integration_test.go b/test/integration/load_balancer_listeners_integration_test.go index d508ce63..dc4b6fec 100644 --- a/test/integration/load_balancer_listeners_integration_test.go +++ b/test/integration/load_balancer_listeners_integration_test.go @@ -1,15 +1,15 @@ //go:build integration -// +build integration package integration import ( "context" "fmt" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationLoadBalancerListener_CRUD(t *testing.T) { @@ -20,7 +20,7 @@ func TestIntegrationLoadBalancerListener_CRUD(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { diff --git a/test/integration/load_balancer_pool_members_integration_test.go b/test/integration/load_balancer_pool_members_integration_test.go index 1ce8dbeb..40b8a1d9 100644 --- a/test/integration/load_balancer_pool_members_integration_test.go +++ b/test/integration/load_balancer_pool_members_integration_test.go @@ -1,14 +1,14 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationLoadBalancerPoolMember_CRUD(t *testing.T) { @@ -19,7 +19,7 @@ func TestIntegrationLoadBalancerPoolMember_CRUD(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { @@ -105,7 +105,7 @@ func TestIntegrationLoadBalancerPoolMember_Update(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) pool, err := createPoolOnLB(lb) if err != nil { @@ -256,7 +256,7 @@ func TestIntegrationLoadBalancerPoolMember_MonitorStatus(t *testing.T) { }() // Step 4: Wait for the load balancer to be running - waitUntilLB(loadBalancer.UUID, t) + waitUntilLB(t, loadBalancer.UUID) // Step 5: Create a load balancer pool poolRequest := &cloudscale.LoadBalancerPoolRequest{ diff --git a/test/integration/load_balancer_pools_integration_test.go b/test/integration/load_balancer_pools_integration_test.go index 58d47cba..55ea6500 100644 --- a/test/integration/load_balancer_pools_integration_test.go +++ b/test/integration/load_balancer_pools_integration_test.go @@ -1,14 +1,14 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationLoadBalancerPool_CRUD(t *testing.T) { @@ -19,7 +19,7 @@ func TestIntegrationLoadBalancerPool_CRUD(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) createLoadBalancerPoolRequest := &cloudscale.LoadBalancerPoolRequest{ Name: testRunPrefix, @@ -81,7 +81,7 @@ func TestIntegrationLoadBalancerPool_Update(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) createLoadBalancerPoolRequest := &cloudscale.LoadBalancerPoolRequest{ Name: testRunPrefix, diff --git a/test/integration/load_balancers_integration_test.go b/test/integration/load_balancers_integration_test.go index e9087d88..e78bc6cd 100644 --- a/test/integration/load_balancers_integration_test.go +++ b/test/integration/load_balancers_integration_test.go @@ -1,14 +1,14 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationLoadBalancer_CRUD(t *testing.T) { @@ -30,7 +30,7 @@ func TestIntegrationLoadBalancer_CRUD(t *testing.T) { t.Fatalf("LoadBalancers.Get returned error %s\n", err) } - waitUntilLB(expected.UUID, t) + waitUntilLB(t, expected.UUID) if h := time.Since(loadBalancer.CreatedAt).Hours(); !(-1 < h && h < 1) { t.Errorf("loadBalancer.CreatedAt ourside of expected range. got=%v", loadBalancer.CreatedAt) @@ -117,7 +117,7 @@ func TestIntegrationLoadBalancer_PrivateNetwork(t *testing.T) { t.Errorf("loadBalancerSubnetUUID \n got=%s\nwant=%s", loadBalancerSubnetUUID, subnet.UUID) } - waitUntilLB(loadBalancer.UUID, t) + waitUntilLB(t, loadBalancer.UUID) err = client.LoadBalancers.Delete(context.Background(), loadBalancer.UUID) if err != nil { @@ -147,7 +147,7 @@ func TestIntegrationLoadBalancer_Update(t *testing.T) { t.Fatalf("loadBalancer.Create returned error %s\n", err) } - waitUntilLB(lb.UUID, t) + waitUntilLB(t, lb.UUID) newName := testRunPrefix + "-renamed" updateRequest := &cloudscale.LoadBalancerRequest{ @@ -175,7 +175,9 @@ func TestIntegrationLoadBalancer_Update(t *testing.T) { } } -func waitUntilLB(uuid string, t *testing.T) *cloudscale.LoadBalancer { +func waitUntilLB(t *testing.T, uuid string) *cloudscale.LoadBalancer { + t.Helper() + lb, err := client.LoadBalancers.WaitFor(context.Background(), uuid, cloudscale.LoadBalancerIsRunning) if err != nil { t.Fatalf("client.LoadBalancers.WaitFor returned error %s\n", err) diff --git a/test/integration/metrics_integration_test.go b/test/integration/metrics_integration_test.go index 25e197b5..ea19871a 100644 --- a/test/integration/metrics_integration_test.go +++ b/test/integration/metrics_integration_test.go @@ -1,13 +1,13 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "testing" "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) func TestIntegrationMetrics_GetBucketMetrics(t *testing.T) { diff --git a/test/integration/networks_integration_test.go b/test/integration/networks_integration_test.go index 6bc753ca..68919198 100644 --- a/test/integration/networks_integration_test.go +++ b/test/integration/networks_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -190,10 +189,8 @@ func TestIntegrationNetwork_CreateAttached(t *testing.T) { if !re.Match([]byte(lastNetworkInterface.Addresses[0].Address)) { t.Errorf("Expected IP regex does not match\ngot=%#v\nwant=%#v", lastNetworkInterface.Addresses[0].Address, tt.expectedIP) } - } else { - if len(lastNetworkInterface.Addresses) != 0 { - t.Errorf("Expected no IP addresses\ngot=%#v", len(lastNetworkInterface.Addresses)) - } + } else if len(lastNetworkInterface.Addresses) != 0 { + t.Errorf("Expected no IP addresses\ngot=%#v", len(lastNetworkInterface.Addresses)) } // this is required especially for the 'without IP' case. @@ -461,6 +458,7 @@ func TestIntegrationNetwork_MultiSite(t *testing.T) { } func createNetworkInZoneAndAssert(t *testing.T, zone cloudscale.ZoneStub) { + t.Helper() createNetworkRequest := &cloudscale.NetworkCreateRequest{ Name: testRunPrefix, diff --git a/test/integration/objects_users_integration_test.go b/test/integration/objects_users_integration_test.go index ff5c8df0..7d66f1da 100644 --- a/test/integration/objects_users_integration_test.go +++ b/test/integration/objects_users_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -27,18 +26,17 @@ func TestIntegrationObjectsUser_CRUD(t *testing.T) { if id := objectsUser.ID; id != expected.ID { t.Errorf("ObjectsUser.ID got=%s\nwant=%s", id, expected.ID) } - if access_key := objectsUser.Keys[0]["access_key"]; access_key != expected.Keys[0]["access_key"] { - t.Errorf("ObjectsUser.Keys[0][\"access_key\"] got=%s\nwant=%s", access_key, expected.Keys[0]["access_key"]) + if accessKey := objectsUser.Keys[0]["access_key"]; accessKey != expected.Keys[0]["access_key"] { + t.Errorf("ObjectsUser.Keys[0][\"access_key\"] got=%s\nwant=%s", accessKey, expected.Keys[0]["access_key"]) } - if secret_key := objectsUser.Keys[0]["secret_key"]; secret_key != expected.Keys[0]["secret_key"] { - t.Errorf("ObjectsUser.Keys[0][\"secret_key\"] got=%s\nwant=%s", secret_key, expected.Keys[0]["secret_key"]) + if secretKey := objectsUser.Keys[0]["secret_key"]; secretKey != expected.Keys[0]["secret_key"] { + t.Errorf("ObjectsUser.Keys[0][\"secret_key\"] got=%s\nwant=%s", secretKey, expected.Keys[0]["secret_key"]) } err = client.ObjectsUsers.Delete(context.Background(), objectsUser.ID) if err != nil { t.Fatalf("ObjectsUsers.Get returned error %s\n", err) } - } func TestIntegrationObjectsUser_UpdateRest(t *testing.T) { @@ -73,10 +71,11 @@ func TestIntegrationObjectsUser_UpdateRest(t *testing.T) { if err != nil { t.Fatalf("ObjectsUsers.Get returned error %s\n", err) } - } func createObjectsUser(t *testing.T) (*cloudscale.ObjectsUser, error) { + t.Helper() + createRequest := &cloudscale.ObjectsUserRequest{ DisplayName: testRunPrefix, } diff --git a/test/integration/regions_integration_test.go b/test/integration/regions_integration_test.go index 923ccce0..dbf7d32b 100644 --- a/test/integration/regions_integration_test.go +++ b/test/integration/regions_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -19,7 +18,7 @@ func TestListRegions(t *testing.T) { t.Fatalf("Regions.List returned error %s\n", err) } - if len(allRegions) <= 0 { + if len(allRegions) == 0 { t.Fatal("Regions.List returned empty slice\n", err) } diff --git a/test/integration/server_groups_integration_test.go b/test/integration/server_groups_integration_test.go index 1a35d246..d581c3ef 100644 --- a/test/integration/server_groups_integration_test.go +++ b/test/integration/server_groups_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -31,10 +30,11 @@ func TestIntegrationServerGroup_CRUD(t *testing.T) { if err != nil { t.Fatalf("ServerGroups.Get returned error %s\n", err) } - } func createServerGroup(t *testing.T) (*cloudscale.ServerGroup, error) { + t.Helper() + createRequest := &cloudscale.ServerGroupRequest{ Name: testRunPrefix + "-group", Type: "anti-affinity", @@ -67,6 +67,7 @@ func TestIntegrationServerGroup_MultiSite(t *testing.T) { } func createServerGroupInZoneAndAssert(t *testing.T, zone cloudscale.ZoneStub) { + t.Helper() createServerGroupRequest := &cloudscale.ServerGroupRequest{ Name: "Yellow Submarine", diff --git a/test/integration/servers_integration_test.go b/test/integration/servers_integration_test.go index 0de5da01..05fc2329 100644 --- a/test/integration/servers_integration_test.go +++ b/test/integration/servers_integration_test.go @@ -1,11 +1,11 @@ //go:build integration -// +build integration package integration import ( "context" "fmt" + "net/http" "reflect" "strings" "testing" @@ -17,6 +17,8 @@ import ( const DefaultImageSlug = "debian-11" func createServer(t *testing.T, createRequest *cloudscale.ServerRequest) (*cloudscale.Server, error) { + t.Helper() + server, err := client.Servers.Create(context.Background(), createRequest) if err != nil { return nil, err @@ -87,7 +89,6 @@ func TestIntegrationServer_CRUD(t *testing.T) { if err != nil { t.Fatalf("Servers.Delete returned error %s\n", err) } - } func TestIntegrationServer_UpdateStatus(t *testing.T) { @@ -207,7 +208,7 @@ func TestIntegrationServer_UpdateRest(t *testing.T) { if !ok { t.Errorf("Couldn't cast %s\n", err) } - if err.StatusCode != 400 { + if err.StatusCode != http.StatusBadRequest { t.Errorf("Expected bad request and not %d\n", err.StatusCode) } if !strings.Contains(err.Error(), expected) { @@ -406,6 +407,7 @@ func TestIntegrationServer_MultiSite(t *testing.T) { } func createServerInZoneAndAssert(t *testing.T, zone cloudscale.ZoneStub) { + t.Helper() createRequest := getDefaultServerRequest() createRequest.Zone = zone.Slug diff --git a/test/integration/subnets_integration_test.go b/test/integration/subnets_integration_test.go index 303d6d71..3b7991eb 100644 --- a/test/integration/subnets_integration_test.go +++ b/test/integration/subnets_integration_test.go @@ -1,13 +1,13 @@ //go:build integration -// +build integration package integration import ( "context" - "github.com/cloudscale-ch/cloudscale-go-sdk/v9" "reflect" "testing" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v9" ) const numberOfDefaultEntries = 2 @@ -143,7 +143,7 @@ func TestIntegrationSubnet_Update(t *testing.T) { } // assert initial DNSServers, no option was passed, hence defaults should be used - if actualDNSServers := subnet.DNSServers; !(len(actualDNSServers) == 2) { + if actualDNSServers := subnet.DNSServers; len(actualDNSServers) != 2 { t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", len(actualDNSServers), 2) } @@ -222,7 +222,7 @@ func TestIntegrationSubnet_Update(t *testing.T) { } // assert default servers - if actualNumberOfEntries := len(updatedSubnet.DNSServers); !(actualNumberOfEntries == numberOfDefaultEntries) { + if actualNumberOfEntries := len(updatedSubnet.DNSServers); actualNumberOfEntries != numberOfDefaultEntries { t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", actualNumberOfEntries, numberOfDefaultEntries) } @@ -242,7 +242,7 @@ func TestIntegrationSubnet_Update(t *testing.T) { } // assert default servers are still set - if actualNumberOfEntries := len(updatedSubnet.DNSServers); !(actualNumberOfEntries == numberOfDefaultEntries) { + if actualNumberOfEntries := len(updatedSubnet.DNSServers); actualNumberOfEntries != numberOfDefaultEntries { t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", actualNumberOfEntries, numberOfDefaultEntries) } @@ -305,7 +305,7 @@ func TestIntegrationSubnet_InitialEmptyDNSServers(t *testing.T) { } // assert default servers - if actualNumberOfEntries := len(updatedSubnet.DNSServers); !(actualNumberOfEntries == numberOfDefaultEntries) { + if actualNumberOfEntries := len(updatedSubnet.DNSServers); actualNumberOfEntries != numberOfDefaultEntries { t.Errorf("Subnet DNSServers length\ngot=%#v\nwant=%#v", actualNumberOfEntries, numberOfDefaultEntries) } diff --git a/test/integration/tags_integration_test.go b/test/integration/tags_integration_test.go index c2343135..a069c9f1 100644 --- a/test/integration/tags_integration_test.go +++ b/test/integration/tags_integration_test.go @@ -1,5 +1,4 @@ //go:build integration -// +build integration package integration @@ -531,7 +530,6 @@ func TestIntegrationTags_ObjectsUser(t *testing.T) { if err != nil { t.Fatalf("ObjectsUsers.Delete returned error %s\n", err) } - } func TestIntegrationTags_Network(t *testing.T) { @@ -602,7 +600,6 @@ func TestIntegrationTags_Network(t *testing.T) { if err != nil { t.Fatalf("Networks.Delete returned error %s\n", err) } - } func TestIntegrationTags_Subnet(t *testing.T) { @@ -687,7 +684,6 @@ func TestIntegrationTags_Subnet(t *testing.T) { if err != nil { t.Fatalf("Networks.Delete returned error %s\n", err) } - } func TestIntegrationTags_ServerGroup(t *testing.T) { @@ -775,7 +771,6 @@ func TestIntegrationTags_ServerGroup(t *testing.T) { if err != nil { t.Fatalf("ServerGroups.Delete returned error %s\n", err) } - } func TestIntegrationTags_CustomImage(t *testing.T) { @@ -876,7 +871,7 @@ func TestIntegrationTags_LoadBalancerAndRelatedResources(t *testing.T) { t.Fatalf("LoadBalancers.Create returned error %s\n", err) } - waitUntilLB(loadBalancer.UUID, t) + waitUntilLB(t, loadBalancer.UUID) getResult, err := client.LoadBalancers.Get(context.Background(), loadBalancer.UUID) if err != nil { @@ -926,16 +921,17 @@ func TestIntegrationTags_LoadBalancerAndRelatedResources(t *testing.T) { } // call these test cases inline to avoid recreating the load balancer - testIntegrationTags_LoadBalancerPool(t, loadBalancer) + testIntegrationTagsLoadBalancerPool(t, loadBalancer) err = client.LoadBalancers.Delete(context.Background(), loadBalancer.UUID) if err != nil { t.Fatalf("LoadBalancers.Delete returned error %s\n", err) } - } -func testIntegrationTags_LoadBalancerPool(t *testing.T, b *cloudscale.LoadBalancer) { +func testIntegrationTagsLoadBalancerPool(t *testing.T, b *cloudscale.LoadBalancer) { + t.Helper() + createRequest := cloudscale.LoadBalancerPoolRequest{ Name: testRunPrefix, Algorithm: "round_robin", @@ -998,9 +994,9 @@ func testIntegrationTags_LoadBalancerPool(t *testing.T, b *cloudscale.LoadBalanc } // call these test cases inline to avoid recreating the load balancer - testIntegrationTags_LoadBalancerListner(t, pool) - testIntegrationTags_LoadBalancerPoolMember(t, pool) - testIntegrationTags_LoadBalancerHealthMonitor(t, pool) + testIntegrationTagsLoadBalancerListner(t, pool) + testIntegrationTagsLoadBalancerPoolMember(t, pool) + testIntegrationTagsLoadBalancerHealthMonitor(t, pool) err = client.LoadBalancerPools.Delete(context.Background(), pool.UUID) if err != nil { @@ -1008,7 +1004,9 @@ func testIntegrationTags_LoadBalancerPool(t *testing.T, b *cloudscale.LoadBalanc } } -func testIntegrationTags_LoadBalancerListner(t *testing.T, p *cloudscale.LoadBalancerPool) { +func testIntegrationTagsLoadBalancerListner(t *testing.T, p *cloudscale.LoadBalancerPool) { + t.Helper() + createRequest := cloudscale.LoadBalancerListenerRequest{ Name: testRunPrefix, Pool: p.UUID, @@ -1076,7 +1074,9 @@ func testIntegrationTags_LoadBalancerListner(t *testing.T, p *cloudscale.LoadBal } } -func testIntegrationTags_LoadBalancerPoolMember(t *testing.T, p *cloudscale.LoadBalancerPool) { +func testIntegrationTagsLoadBalancerPoolMember(t *testing.T, p *cloudscale.LoadBalancerPool) { + t.Helper() + network, subnet, err := createNetworkAndSubnet() if err != nil { t.Fatalf("error while creating network and subnet: %s\n", err) @@ -1154,7 +1154,9 @@ func testIntegrationTags_LoadBalancerPoolMember(t *testing.T, p *cloudscale.Load } } -func testIntegrationTags_LoadBalancerHealthMonitor(t *testing.T, p *cloudscale.LoadBalancerPool) { +func testIntegrationTagsLoadBalancerHealthMonitor(t *testing.T, p *cloudscale.LoadBalancerPool) { + t.Helper() + createRequest := cloudscale.LoadBalancerHealthMonitorRequest{ Pool: p.UUID, DelayS: 10, diff --git a/test/integration/volume_snapshots_integration_test.go b/test/integration/volume_snapshots_integration_test.go index 36d12b49..664125db 100644 --- a/test/integration/volume_snapshots_integration_test.go +++ b/test/integration/volume_snapshots_integration_test.go @@ -5,6 +5,7 @@ package integration import ( "context" "fmt" + "net/http" "testing" "time" @@ -150,12 +151,11 @@ func TestIntegrationVolumeSnapshot_Update(t *testing.T) { // waitForSnapshotDeletion polls the API until the snapshot no longer exists func waitForSnapshotDeletion(ctx context.Context, snapshotUUID string, maxWaitSeconds int) error { - for i := 0; i < maxWaitSeconds; i++ { + for range maxWaitSeconds { snapshot, err := client.VolumeSnapshots.Get(ctx, snapshotUUID) if err != nil { - if apiErr, ok := err.(*cloudscale.ErrorResponse); ok { - if apiErr.StatusCode == 404 { + if apiErr.StatusCode == http.StatusNotFound { // if we get a 404 error, snapshot is gone, deletion completed return nil } diff --git a/test/integration/volumes_integration_test.go b/test/integration/volumes_integration_test.go index 72bc2055..2ceac714 100644 --- a/test/integration/volumes_integration_test.go +++ b/test/integration/volumes_integration_test.go @@ -1,11 +1,11 @@ //go:build integration -// +build integration package integration import ( "context" "fmt" + "net/http" "strings" "testing" "time" @@ -187,7 +187,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { if !ok { t.Errorf("Couldn't cast %s\n", err) } - if err.StatusCode != 400 { + if err.StatusCode != http.StatusBadRequest { t.Errorf("Expected bad request and not %d\n", err.StatusCode) } if !strings.Contains(err.Error(), expected) { @@ -199,6 +199,9 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { // Try to scale. scaleVolumeRequest := &cloudscale.VolumeUpdateRequest{SizeGB: scaleSize} err = client.Volumes.Update(context.TODO(), volume.UUID, scaleVolumeRequest) + if err != nil { + t.Errorf("Volumes.Update returned error %s\n", err) + } getVolume, err := client.Volumes.Get(context.TODO(), volume.UUID) if err == nil { if getVolume.SizeGB != scaleSize { @@ -343,6 +346,7 @@ func TestIntegrationVolume_MultiSite(t *testing.T) { } func createVolumeInZoneAndAssert(t *testing.T, zone cloudscale.ZoneStub) { + t.Helper() createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: testRunPrefix, diff --git a/volumes.go b/volumes.go index 2413c0fb..1e0db83c 100644 --- a/volumes.go +++ b/volumes.go @@ -1,7 +1,6 @@ package cloudscale import ( - "fmt" "net/http" "time" ) @@ -54,7 +53,7 @@ type VolumeService interface { func WithNameFilter(name string) ListRequestModifier { return func(request *http.Request) { query := request.URL.Query() - query.Add(fmt.Sprintf("name"), name) + query.Add("name", name) request.URL.RawQuery = query.Encode() } }