Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea/
/vendor/
bin/
97 changes: 97 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -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
120 changes: 112 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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<target>\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 )
Expand All @@ -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
10 changes: 4 additions & 6 deletions cloudscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 7 additions & 7 deletions cloudscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package cloudscale
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -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)
}
Expand All @@ -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) {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
Loading