diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6334b521..8dfdb33e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,14 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: + # 'stable' on purpose, and deliberately NOT the pinned version used + # by golangci-lint.yml. Building on the current Go is this job's + # whole point: it is the signal that these samples still compile + # against the latest release. The lint job pins instead, because a + # linter has to understand the compiler's export-data format and a + # floating toolchain there silently outruns it. Every sample's + # go.mod is <= the lint pin, so the lint toolchain can still build + # all of them. go-version: 'stable' - name: Build projects diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index f916a81d..f56b593f 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -70,18 +70,20 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: 'stable' + # Pinned deliberately, NOT 'stable'. golangci-lint reads the + # compiler's export data, and each Go release can bump that format: + # a floating toolchain silently outran the pinned linter and broke + # every job in this matrix with "export data version 4 is greater + # than maximum supported version 2" -- with no change to any sample. + # Bumping Go here must be a deliberate edit paired with the linter + # version below. + go-version: '1.27' cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v8 with: - # Require: The version of golangci-lint to use. - # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. - # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.63.4 + # Keep in step with go-version above; see the note there. + version: v2.13.2 # Optional: working directory, useful for monorepos working-directory: ${{matrix.working-directory}} - - # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. - install-mode: "goinstall" diff --git a/.golangci.yml b/.golangci.yml index 1d2e6c2c..a462a02c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,48 +1,74 @@ -# This is the configuration for golangci-lint for the restic project. +# golangci-lint configuration, v2 schema. # -# A sample config with all settings is here: -# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +# Migrated from the v1 schema with `golangci-lint migrate`. Two v1 settings +# have no direct v2 spelling and are preserved structurally instead -- see the +# notes on `linters.exclusions` below before editing. +version: "2" linters: - # only enable the linters listed below - disable-all: true + # v1 said `disable-all: true`; v2 spells it `default: none`. + default: none enable: # make sure all errors returned by functions are handled - errcheck - # show how code can be simplified - - gosimple - - # # make sure code is formatted - - gofmt - # examine code and report suspicious constructs, such as Printf calls whose # arguments do not align with the format string - govet - # make sure names and comments are used according to the conventions - - revive - # detect when assignments to existing variables are not used - ineffassign - # run static analysis and find errors + # make sure names and comments are used according to the conventions + - revive + + # run static analysis and find errors. + # + # Also covers what v1 listed separately as `gosimple`: the S1xxx checks + # moved inside staticcheck in v2, so gosimple is not a separate linter + # any more. v1's `typecheck` is likewise gone as a linter -- compile + # errors are always surfaced -- so neither entry is missing here. - staticcheck # find unused variables, functions, structs, types, etc. - unused - # parse and typecheck code - - typecheck + exclusions: + # DELIBERATELY NO `presets:` KEY HERE. + # + # This is how v1's `issues.exclude-use-default: false` survives the + # migration: v2 applies no exclusion presets unless you list them. Adding + # `presets: [comments, std-error-handling, common-false-positives, legacy]` + # -- which is what most v2 templates and a re-run of `golangci-lint + # migrate` will hand you -- silently switches OFF errcheck for ignored + # errors from Close() calls, among others. That was the original reason + # for the v1 setting. Leave this key absent. + generated: lax + rules: + # The `path` on each rule is REQUIRED, not decoration: v2 rejects a rule + # with fewer than 2 of (text, source, path, linters) set -- + # "at least 2 of (text, source, path[-except], linters) should be set". + # `(.+)\.go$` is the widest path that satisfies it, keeping these + # equivalent to v1's file-agnostic `issues.exclude` entries. + # + # revive: do not warn about missing comments for exported stuff + - path: (.+)\.go$ + text: exported (function|method|var|type|const) .* should have comment or be unexported + # revive: ignore constants in all caps + - path: (.+)\.go$ + text: don't use ALL_CAPS in Go names; use CamelCase + paths: + - third_party$ + - builtin$ + - examples$ -issues: - # don't use the default exclude rules, this hides (among others) ignored - # errors from Close() calls - exclude-use-default: false - - # list of things to not warn about - exclude: - # revive: do not warn about missing comments for exported stuff - - exported (function|method|var|type|const) .* should have comment or be unexported - # revive: ignore constants in all caps - - don't use ALL_CAPS in Go names; use CamelCase \ No newline at end of file +formatters: + # v1 listed gofmt under `linters`; v2 moves formatters to their own section. + enable: + - gofmt + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/go-docker-timefreeze/main.go b/go-docker-timefreeze/main.go index af4e7759..b5a5c743 100644 --- a/go-docker-timefreeze/main.go +++ b/go-docker-timefreeze/main.go @@ -69,7 +69,7 @@ func insecureExpiryOnlyMiddleware(next http.Handler) http.Handler { return } - if claims.ExpiresAt.Time.Before(time.Now()) { + if claims.ExpiresAt.Before(time.Now()) { http.Error(w, fmt.Sprintf("Token is expired. Current timestamp: %d", time.Now().Unix()), http.StatusUnauthorized) return } diff --git a/go-memory-load/docker-compose.yml b/go-memory-load/docker-compose.yml index 56f1ba33..cbcdff2a 100644 --- a/go-memory-load/docker-compose.yml +++ b/go-memory-load/docker-compose.yml @@ -12,7 +12,21 @@ services: - postgres_data:/var/lib/postgresql/data - ./db/init:/docker-entrypoint-initdb.d:ro healthcheck: - test: ["CMD-SHELL", "pg_isready -U app_user -d orderdb"] + # -h 127.0.0.1 is load-bearing: it forces a TCP probe. + # + # Without it pg_isready talks to the Unix socket, and postgres' + # docker-entrypoint runs a *temporary* server reachable only over that + # socket (listen_addresses='') once initdb has finished, to create the + # database and run docker-entrypoint-initdb.d. The socket therefore + # answers "accepting connections" while no TCP listener exists at all, + # so compose marks this service healthy and depends_on: + # service_healthy releases the dependent straight into ECONNREFUSED. + # + # Short but real: ~235ms with no initdb.d scripts, and 1.2s of + # false-healthy plus a 2.7s shutdown checkpoint in the CI failure this + # was diagnosed from. Only bites on a fresh volume -- a populated one + # skips the temporary server entirely, which is why it failed rarely. + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U app_user -d orderdb"] interval: 5s timeout: 5s retries: 20 diff --git a/http-postgres/docker-compose.yml b/http-postgres/docker-compose.yml index 7e95445b..13f01690 100644 --- a/http-postgres/docker-compose.yml +++ b/http-postgres/docker-compose.yml @@ -10,7 +10,21 @@ services: volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + # -h 127.0.0.1 is load-bearing: it forces a TCP probe. + # + # Without it pg_isready talks to the Unix socket, and postgres' + # docker-entrypoint runs a *temporary* server reachable only over that + # socket (listen_addresses='') once initdb has finished, to create the + # database and run docker-entrypoint-initdb.d. The socket therefore + # answers "accepting connections" while no TCP listener exists at all, + # so compose marks this service healthy and depends_on: + # service_healthy releases the dependent straight into ECONNREFUSED. + # + # Short but real: ~235ms with no initdb.d scripts, and 1.2s of + # false-healthy plus a 2.7s shutdown checkpoint in the CI failure this + # was diagnosed from. Only bites on a fresh volume -- a populated one + # skips the temporary server entirely, which is why it failed rarely. + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres"] interval: 2s timeout: 5s retries: 5 diff --git a/mux-mysql/db/database.go b/mux-mysql/db/database.go index 813bfcd4..3997f963 100644 --- a/mux-mysql/db/database.go +++ b/mux-mysql/db/database.go @@ -31,7 +31,7 @@ func GetWebsiteFromID(id string, db *sql.DB) (string, error) { err := row.Scan(&link) if err != nil { if err == sql.ErrNoRows { - return "", fmt.Errorf("Website not found") + return "", fmt.Errorf("website not found") } return "", err } diff --git a/postgres-wire-features/docker-compose.yml b/postgres-wire-features/docker-compose.yml index 323b38ca..9ab6e4df 100644 --- a/postgres-wire-features/docker-compose.yml +++ b/postgres-wire-features/docker-compose.yml @@ -20,7 +20,21 @@ services: volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + # -h 127.0.0.1 is load-bearing: it forces a TCP probe. + # + # Without it pg_isready talks to the Unix socket, and postgres' + # docker-entrypoint runs a *temporary* server reachable only over that + # socket (listen_addresses='') once initdb has finished, to create the + # database and run docker-entrypoint-initdb.d. The socket therefore + # answers "accepting connections" while no TCP listener exists at all, + # so compose marks this service healthy and depends_on: + # service_healthy releases the dependent straight into ECONNREFUSED. + # + # Short but real: ~235ms with no initdb.d scripts, and 1.2s of + # false-healthy plus a 2.7s shutdown checkpoint in the CI failure this + # was diagnosed from. Only bites on a fresh volume -- a populated one + # skips the temporary server entirely, which is why it failed rarely. + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres"] interval: 2s timeout: 5s retries: 5 diff --git a/proxy-stress-test/docker-compose.yml b/proxy-stress-test/docker-compose.yml index d54d0803..d2fbdf47 100644 --- a/proxy-stress-test/docker-compose.yml +++ b/proxy-stress-test/docker-compose.yml @@ -10,7 +10,21 @@ services: volumes: - ./init.sql:/docker-entrypoint-initdb.d/01-init.sql healthcheck: - test: ["CMD-SHELL", "pg_isready -U repro -d reprodb"] + # -h 127.0.0.1 is load-bearing: it forces a TCP probe. + # + # Without it pg_isready talks to the Unix socket, and postgres' + # docker-entrypoint runs a *temporary* server reachable only over that + # socket (listen_addresses='') once initdb has finished, to create the + # database and run docker-entrypoint-initdb.d. The socket therefore + # answers "accepting connections" while no TCP listener exists at all, + # so compose marks this service healthy and depends_on: + # service_healthy releases the dependent straight into ECONNREFUSED. + # + # Short but real: ~235ms with no initdb.d scripts, and 1.2s of + # false-healthy plus a 2.7s shutdown checkpoint in the CI failure this + # was diagnosed from. Only bites on a fresh volume -- a populated one + # skips the temporary server entirely, which is why it failed rarely. + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U repro -d reprodb"] interval: 2s timeout: 5s retries: 10 diff --git a/ps-cache-postgres/docker-compose.yml b/ps-cache-postgres/docker-compose.yml index 6a7e6d8a..a61d2590 100644 --- a/ps-cache-postgres/docker-compose.yml +++ b/ps-cache-postgres/docker-compose.yml @@ -10,7 +10,21 @@ services: volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + # -h 127.0.0.1 is load-bearing: it forces a TCP probe. + # + # Without it pg_isready talks to the Unix socket, and postgres' + # docker-entrypoint runs a *temporary* server reachable only over that + # socket (listen_addresses='') once initdb has finished, to create the + # database and run docker-entrypoint-initdb.d. The socket therefore + # answers "accepting connections" while no TCP listener exists at all, + # so compose marks this service healthy and depends_on: + # service_healthy releases the dependent straight into ECONNREFUSED. + # + # Short but real: ~235ms with no initdb.d scripts, and 1.2s of + # false-healthy plus a 2.7s shutdown checkpoint in the CI failure this + # was diagnosed from. Only bites on a fresh volume -- a populated one + # skips the temporary server entirely, which is why it failed rarely. + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres"] interval: 2s timeout: 5s retries: 5