diff --git a/.golangci.yml b/.golangci.yml index 15b99171..ff56f3ef 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,11 @@ +version: "2" + run: - timeout: 30m + modules-download-mode: vendor build-tags: - containers_image_openpgp - exclude_graphdriver_btrfs - exclude_graphdriver_devicemapper - # default uses Go version from the go.mod file, fallback on the env var - # `GOVERSION`, fallback on 1.17: https://golangci-lint.run/usage/configuration/#run-configuration - go: "1.23" linters: enable: @@ -14,10 +13,7 @@ linters: - depguard - forbidigo - gocritic - - gofmt - - goimports - gosec - - gosimple - govet - ineffassign - makezero @@ -27,79 +23,102 @@ linters: - revive - staticcheck - testifylint - - typecheck - unused - whitespace - disable-all: true - -linters-settings: - gocritic: - disabled-checks: - - "ifElseChain" - - "assignOp" - - "appendAssign" - - "singleCaseSwitch" - - "exitAfterDefer" # FIXME - importas: - alias: - - pkg: "github.com/opencontainers/image-spec/specs-go/v1" - alias: "ocispecs" - - pkg: "github.com/opencontainers/go-digest" - alias: "digest" - govet: - enable: - - nilness - - unusedwrite - depguard: + settings: + gocritic: + disabled-checks: + - "ifElseChain" + - "assignOp" + - "appendAssign" + - "singleCaseSwitch" + importas: + alias: + - pkg: "github.com/opencontainers/image-spec/specs-go/v1" + alias: "ocispecs" + - pkg: "github.com/opencontainers/go-digest" + alias: "digest" + govet: + enable: + - nilness + - unusedwrite + depguard: + rules: + main: + deny: + - pkg: "github.com/containerd/containerd/errdefs" + desc: The containerd errdefs package was migrated to a separate module. Use github.com/containerd/errdefs instead. + - pkg: "github.com/containerd/containerd/log" + desc: The containerd log package was migrated to a separate module. Use github.com/containerd/log instead. + - pkg: "github.com/containerd/containerd/platforms" + desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead. + - pkg: "io/ioutil" + desc: The io/ioutil package has been deprecated. + forbidigo: + forbid: + - pattern: ^context\.WithCancel(# use context\.WithCancelCause instead)?$ + - pattern: ^context\.WithDeadline(# use context\.WithDeadline instead)?$ + - pattern: ^context\.WithTimeout(# use context\.WithTimeoutCause instead)?$ + - pattern: ^ctx\.Err(# use context\.Cause instead)?$ + - pattern: ^fmt\.Errorf(# use errors\.Errorf instead)?$ + - pattern: ^platforms\.DefaultString(# use platforms\.Format(platforms\.DefaultSpec()) instead\.)?$ + gosec: + excludes: + - G204 + - G402 + - G115 + config: + G306: "0644" + testifylint: + disable: + - "empty" + - "bool-compare" + - "len" + - "negative-positive" + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - main: - deny: - - pkg: "github.com/containerd/containerd/platforms" - desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead. - - pkg: "io/ioutil" - desc: The io/ioutil package has been deprecated. - forbidigo: - forbid: - - '^fmt\.Errorf(# use errors\.Errorf instead)?$' - - '^platforms\.DefaultString(# use platforms\.Format(platforms\.DefaultSpec()) instead\.)?$' - gosec: - excludes: - - G204 # Audit use of command execution - - G402 # TLS MinVersion too low - - G115 # integer overflow conversion - config: - G306: "0644" - testifylint: - disable: - # disable rules that reduce the test condition - - "empty" - - "bool-compare" - - "len" - - "negative-positive" + - + linters: + - revive + text: stutters + - + linters: + - revive + text: empty-block + - + linters: + - revive + text: superfluous-else + - + linters: + - revive + text: unused-parameter + - + linters: + - revive + text: redefines-builtin-id + - + linters: + - revive + text: if-return + paths: + - .*\.pb\.go$ + +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - .*\.pb\.go$ issues: - exclude-files: - - ".*\\.pb\\.go$" - exclude-rules: - - linters: - - revive - text: "stutters" - - linters: - - revive - text: "empty-block" - - linters: - - revive - text: "superfluous-else" - - linters: - - revive - text: "unused-parameter" - - linters: - - revive - text: "redefines-builtin-id" - - linters: - - revive - text: "if-return" - - # show all max-issues-per-linter: 0 max-same-issues: 0 diff --git a/cmd/serve.go b/cmd/serve.go index fe1c83ce..c8bbbdbf 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -79,7 +79,7 @@ func (s *ServeCmd) Run(ctx *Context) error { case "block": defer profile.Start(profile.BlockProfile, profilePath).Stop() default: - log.Fatal().Msgf("Unknown profiler: %s", s.Profiler) + log.Fatal().Msgf("Unknown profiler: %s", s.Profiler) //nolint:gocritic // defer not set if profiler is unknown } } diff --git a/docker-bake.hcl b/docker-bake.hcl index 0f5d6e4e..b108cbb5 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -16,6 +16,10 @@ variable "GOLANGCI_LINT_MULTIPLATFORM" { default = null } +variable "GOLANGCI_FROM_SOURCE" { + default = null +} + target "_common" { args = { GO_VERSION = GO_VERSION @@ -134,6 +138,9 @@ target "lint" { inherits = ["_common"] dockerfile = "./hack/lint.Dockerfile" target = "lint" + args = { + GOLANGCI_FROM_SOURCE = GOLANGCI_FROM_SOURCE + } output = ["type=cacheonly"] platforms = GOLANGCI_LINT_MULTIPLATFORM != null ? [ "darwin/amd64", diff --git a/hack/lint.Dockerfile b/hack/lint.Dockerfile index 9c6b19e7..10be9cd1 100644 --- a/hack/lint.Dockerfile +++ b/hack/lint.Dockerfile @@ -3,20 +3,34 @@ ARG GO_VERSION="1.23" ARG XX_VERSION="1.6.1" ARG ALPINE_VERSION="3.21" -ARG GOLANGCI_LINT_VERSION="v1.62" +ARG GOLANGCI_LINT_VERSION="v2.1.6" +ARG GOLANGCI_FROM_SOURCE="true" FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx + FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base ENV GOFLAGS="-buildvcs=false" RUN apk add --no-cache gcc linux-headers musl-dev COPY --from=xx --link / / WORKDIR /src +FROM base AS golangci-build +ARG GOLANGCI_LINT_VERSION +ADD "https://github.com/golangci/golangci-lint.git#${GOLANGCI_LINT_VERSION}" . +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/ go mod download +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/ mkdir -p out && go build -o /out/golangci-lint ./cmd/golangci-lint + FROM --platform=$BUILDPLATFORM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint +FROM scratch AS golangci-binary-false +COPY --from=golangci-lint /usr/bin/golangci-lint golangci-lint +FROM scratch AS golangci-binary-true +COPY --from=golangci-build /out/golangci-lint golangci-lint +FROM golangci-binary-${GOLANGCI_FROM_SOURCE} AS golangci-binary + FROM base AS lint ARG TARGETPLATFORM RUN --mount=type=bind,target=. \ --mount=type=cache,target=/root/.cache,id=lint-cache-$TARGETPLATFORM \ - --mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \ + --mount=from=golangci-binary,source=/golangci-lint,target=/usr/bin/golangci-lint \ xx-go --wrap && \ golangci-lint run ./... diff --git a/internal/notif/apprise/client.go b/internal/notif/apprise/client.go index 0e1952dd..e79edcdd 100644 --- a/internal/notif/apprise/client.go +++ b/internal/notif/apprise/client.go @@ -87,11 +87,12 @@ func (c *Client) Send(entry model.NotifEntry) error { q := u.Query() u.RawQuery = q.Encode() - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", u.String(), dataBuf) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", u.String(), dataBuf) if err != nil { return err } diff --git a/internal/notif/discord/client.go b/internal/notif/discord/client.go index 5b5fd157..cad9ba9c 100644 --- a/internal/notif/discord/client.go +++ b/internal/notif/discord/client.go @@ -128,11 +128,12 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", u.String(), dataBuf) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", u.String(), dataBuf) if err != nil { return err } diff --git a/internal/notif/gotify/client.go b/internal/notif/gotify/client.go index de51bd07..c7c83020 100644 --- a/internal/notif/gotify/client.go +++ b/internal/notif/gotify/client.go @@ -89,11 +89,12 @@ func (c *Client) Send(entry model.NotifEntry) error { q.Set("token", token) u.RawQuery = q.Encode() - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewBuffer(jsonBody)) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", u.String(), bytes.NewBuffer(jsonBody)) if err != nil { return err } diff --git a/internal/notif/ntfy/client.go b/internal/notif/ntfy/client.go index dc26f64a..39182dde 100644 --- a/internal/notif/ntfy/client.go +++ b/internal/notif/ntfy/client.go @@ -81,11 +81,12 @@ func (c *Client) Send(entry model.NotifEntry) error { q := u.Query() u.RawQuery = q.Encode() - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", u.String(), dataBuf) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", u.String(), dataBuf) if err != nil { return err } diff --git a/internal/notif/rocketchat/client.go b/internal/notif/rocketchat/client.go index cb20e94e..b0264834 100644 --- a/internal/notif/rocketchat/client.go +++ b/internal/notif/rocketchat/client.go @@ -122,11 +122,12 @@ func (c *Client) Send(entry model.NotifEntry) error { } u.Path = path.Join(u.Path, "api/v1/chat.postMessage") - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", u.String(), dataBuf) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", u.String(), dataBuf) if err != nil { return err } diff --git a/internal/notif/signalrest/client.go b/internal/notif/signalrest/client.go index c1b37f74..0da3317b 100644 --- a/internal/notif/signalrest/client.go +++ b/internal/notif/signalrest/client.go @@ -9,6 +9,7 @@ import ( "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" + "github.com/pkg/errors" ) // Client represents an active signalrest notification object @@ -62,11 +63,12 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", c.cfg.Endpoint, bytes.NewBuffer(body)) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", c.cfg.Endpoint, bytes.NewBuffer(body)) if err != nil { return err } diff --git a/internal/notif/teams/client.go b/internal/notif/teams/client.go index 200bbec0..b1b776df 100644 --- a/internal/notif/teams/client.go +++ b/internal/notif/teams/client.go @@ -110,11 +110,12 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, 10*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", webhookURL, bytes.NewBuffer(jsonBody)) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", webhookURL, bytes.NewBuffer(jsonBody)) if err != nil { return err } diff --git a/internal/notif/webhook/client.go b/internal/notif/webhook/client.go index c6b4de13..56853e80 100644 --- a/internal/notif/webhook/client.go +++ b/internal/notif/webhook/client.go @@ -8,6 +8,7 @@ import ( "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" + "github.com/pkg/errors" ) // Client represents an active webhook notification object @@ -47,11 +48,12 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - hc := http.Client{} - ctx, cancel := context.WithTimeout(context.Background(), *c.cfg.Timeout) - defer cancel() + cancelCtx, cancel := context.WithCancelCause(context.Background()) + timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + defer func() { cancel(errors.WithStack(context.Canceled)) }() - req, err := http.NewRequestWithContext(ctx, "POST", c.cfg.Endpoint, bytes.NewBuffer(body)) + hc := http.Client{} + req, err := http.NewRequestWithContext(timeoutCtx, "POST", c.cfg.Endpoint, bytes.NewBuffer(body)) if err != nil { return err } diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go index 538f1b20..dbd2ef51 100644 --- a/pkg/k8s/client.go +++ b/pkg/k8s/client.go @@ -68,7 +68,7 @@ func newInClusterClient(opts Options) (*kubernetes.Clientset, error) { config.Host = opts.Endpoint } if opts.TLSInsecure != nil { - config.TLSClientConfig.Insecure = *opts.TLSInsecure + config.Insecure = *opts.TLSInsecure } return kubernetes.NewForConfig(config) @@ -80,7 +80,7 @@ func newExternalClusterClientFromFile(opts Options, file string) (*kubernetes.Cl return nil, err } if opts.TLSInsecure != nil { - configFromFlags.TLSClientConfig.Insecure = *opts.TLSInsecure + configFromFlags.Insecure = *opts.TLSInsecure } return kubernetes.NewForConfig(configFromFlags) @@ -113,7 +113,7 @@ func newExternalClusterClient(opts Options) (*kubernetes.Clientset, error) { } } if opts.TLSInsecure != nil { - config.TLSClientConfig.Insecure = *opts.TLSInsecure + config.Insecure = *opts.TLSInsecure } return kubernetes.NewForConfig(config) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 30a7f01b..dc1f4f36 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -5,6 +5,7 @@ import ( "time" "github.com/containers/image/v5/types" + "github.com/pkg/errors" ) // Client represents an active docker registry object @@ -43,9 +44,11 @@ func New(opts Options) (*Client, error) { func (c *Client) timeoutContext() (context.Context, context.CancelFunc) { ctx := context.Background() - var cancel context.CancelFunc = func() {} + var cancelFunc context.CancelFunc = func() {} if c.opts.Timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, c.opts.Timeout) + cancelCtx, cancel := context.WithCancelCause(ctx) + ctx, _ = context.WithTimeoutCause(cancelCtx, c.opts.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent + cancelFunc = func() { cancel(errors.WithStack(context.Canceled)) } } - return ctx, cancel + return ctx, cancelFunc }