Skip to content

ci: bump Go to 1.26 and golangci-lint to v2 to unblock dependency updates - #1463

Closed
Iamrushabhshahh wants to merge 3 commits into
KusionStack:mainfrom
Iamrushabhshahh:ci/bump-go-1.26
Closed

Iamrushabhshahh wants to merge 3 commits into
KusionStack:mainfrom
Iamrushabhshahh:ci/bump-go-1.26

Conversation

@Iamrushabhshahh

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind cleanup

What this PR does / why we need it:

Unblocks #1452, #1454, #1457, #1459 and #1460.

What is broken

Every open Dependabot Go module PR fails the Golang Lint and Unit tests with coverage jobs:

PR Bump Failing run
#1452 go-git 5.13.0 to 5.19.2 https://github.com/KusionStack/kusion/actions/runs/32545277078
#1454 x/crypto 0.31.0 to 0.52.0 https://github.com/KusionStack/kusion/actions/runs/32545284802
#1457 grpc 1.69.0 to 1.83.1 https://github.com/KusionStack/kusion/actions/runs/33594898558
#1459 oras-go 2.5.0 to 2.6.2 https://github.com/KusionStack/kusion/actions/runs/35257493310
#1460 containerd 1.7.23 to 1.7.36 https://github.com/KusionStack/kusion/actions/runs/36183219532

All five raise the go directive in go.mod from 1.22.1 to 1.25.0, because the new versions need Go 1.25. The #1451 description already noted this: go-git v5.19.2 needs Go >= 1.25 while CI is pinned to 1.22.

Root cause

CI installs Go 1.22 and golangci-lint v1.56.2. With go 1.25.0 in go.mod, Go 1.22 downloads a Go 1.25 toolchain on its own (GOTOOLCHAIN=auto), so go build works. Three things then break.

  1. golangci-lint v1 cannot type check code for a newer Go. From the chore(deps): bump github.com/containerd/containerd from 1.7.23 to 1.7.36 #1460 lint job:

    ##[error]could not import k8s.io/apimachinery/pkg/types (-: could not load export data: internal error in importing "k8s.io/apimachinery/pkg/types" (unsupported version: 2); please report an issue) (typecheck)
    ##[error]undefined: chi (typecheck)
    ##[error]o.RefProject undefined (type *ApplyOptions has no field or method RefProject) (typecheck)
    

    The last v1 release (v1.64.8, built with Go 1.24) refuses outright. I ran it locally on the chore(deps): bump github.com/containerd/containerd from 1.7.23 to 1.7.36 #1460 head:

    Error: can't load config: the Go language version (go1.24) used to build golangci-lint is lower than the targeted Go version (1.25.0)
    

    So golangci-lint has to move to v2, and .golangci.yml has to move to the v2 format.

  2. go vet in newer Go reports real problems. From the chore(deps): bump github.com/containerd/containerd from 1.7.23 to 1.7.36 #1460 unit test job (go vet ./... step):

    ##[error]pkg/server/route/route.go:201:74: slog.Logger.Error arg "err" should be a string or a slog.Attr (possible missing key or value)
    ##[error]pkg/engine/operation/watch.go:71:22: non-constant format string in call to fmt.Errorf
    ##[error]third_party/dyff/output_human.go:246:12: non-constant format string in call to kusionstack.io/kusion/third_party/dyff.italic
    

    The slog finding shows up with Go 1.26 even on the current go.mod. The format string findings show up once go.mod says 1.24 or later.

  3. Once vet passes, every test binary that imports mockey fails to link on Go 1.23 and later. I hit this in my fork's CI:

    link: github.com/bytedance/mockey/internal/monkey/common: invalid reference to runtime.sysAllocOS
    FAIL	kusionstack.io/kusion/pkg/backend [build failed]
    

    mockey v1.2.10 uses a go:linkname that newer linkers block.

What this PR changes

There are three commits.

ci: bump Go to 1.26 and golangci-lint to v2 ...

  • .github/workflows/test.yaml, e2e-test.yml, release.yaml: go-version: '1.26'; actions/checkout@v7, actions/setup-go@v7, actions/setup-node@v7 (Node 24 runtime); golangci/golangci-lint-action@v9 with version: v2.14.0; shogo82148/actions-goveralls@v1.11.2.
  • .github/workflows/constraint.yaml: actions/checkout@v7.
  • Dockerfile: golang:1.22 to golang:1.26. The official Go images set GOTOOLCHAIN=local, so the image build would break as soon as go.mod says 1.25.
  • Makefile: GOLINTER_VERSION to v2.14.0 and the v2 module path for go install.
  • .golangci.yml: migrated to v2 with golangci-lint migrate, then tidied by hand to keep the v1 behaviour:
    • same linters. gosimple and stylecheck are now part of staticcheck. exportloopref is gone (it was already inactive). typecheck cannot be listed any more. gofumpt moves to formatters.
    • staticcheck runs its default checks minus the new QF* quick fixes, which v1 never ran.
    • govet's new inline analyser is disabled, as v1 did not have it.
    • v1's default exclusions are kept through the comments, common-false-positives, legacy and std-error-handling presets, and the existing exclude texts are kept as rules.
  • Code, for the go vet findings:
    • pkg/server/route/route.go, pkg/server/handler/endpoint/endpoint.go: logger.Error(err.Error(), "msg", "error", err) becomes logger.Error("msg", "error", err). The old calls logged the error as the message and produced a !BADKEY attribute.
    • pkg/engine/operation/watch.go, pkg/engine/runtime/terraform/tfops/tferrors.go: fmt.Errorf(s) becomes errors.New(s).
    • pkg/engine/operation/models/change.go: fmt.Printf(s) becomes fmt.Print(s).
    • third_party/dyff/output_human.go: red(s) becomes red("%s", s) and so on. The output is identical.

test: bump mockey to v1.4.6 ...

  • go.mod, go.sum: mockey v1.2.10 to v1.4.6. Its release notes say it supports Go 1.26 and it still declares go 1.18, so our go directive stays at 1.22.1. go mod tidy also moves three test-only indirect dependencies (goconvey, assertions, gopherjs).

style: fix findings reported by golangci-lint v2

The newer linters inside v2 report 26 findings in pkg/. All of them are mechanical:

  • staticcheck SA1006 (12): fmt.Errorf(errConst) becomes errors.New(errConst) in the secrets providers.
  • staticcheck S1009 (2): drop x == nil || before len(x) == 0.
  • predeclared (4): local max in k8s_printer.go shadows the Go 1.21 builtin, so I renamed it to maxItems.
  • prealloc (7): preallocate six slices in non-test code. The seventh is a table-driven test, so I added a narrow rule that skips prealloc in _test.go files.
  • gofumpt (1): reformat k8s_printer.go (*(x) becomes *x).
  • The v2 migration wrote the skipped directories as third_party$, which does not match files inside them. I changed them to (^|/)third_party($|/), which is the pattern v1 used, so third_party stays out of lint as before.

Why this option

  • Go 1.26, not 1.25. The dependencies need at least 1.25, but Go 1.25 no longer gets security fixes now that Go 1.27 is out. The supported releases are 1.26 and 1.27 (go.dev/dl lists go1.27.2 and go1.26.9 as the current stable releases). Release binaries are built in CI, so I picked the older supported release. go.mod keeps its own minimum, so Go 1.25 users can still build.
  • go.mod go directive left at 1.22.1. Each Dependabot PR raises it when it needs to. That keeps this PR to CI and tooling, and the Dependabot PRs rebase on top unchanged.
  • golangci-lint v2, not the last v1. v1.64.8 cannot load a Go 1.25 module (error above), and v1 is no longer maintained. golangci-lint-action v8 and later only support v2.
  • Fix findings, do not silence them. Each fix is small, and two of them (the slog calls and the format strings) are real bugs. The only new exclusion is prealloc in tests.

How I tested it

Locally on macOS arm64 with GOTOOLCHAIN=go1.26.9:

$ go build ./...                     # exit 0
$ go vet ./...                       # exit 0
$ golangci-lint config verify        # v2.14.0, OK
$ golangci-lint run --concurrency=2 ./pkg/secrets/... ./pkg/engine/printers/... ./pkg/oci/... \
    ./pkg/util/yaml/... ./pkg/workspace/... ./pkg/cmd/project/... ./pkg/cmd/resource/... \
    ./pkg/engine/operation/... ./pkg/server/... ./pkg/engine/runtime/terraform/tfops/...
0 issues.
$ go test -gcflags="all=-l -N" ./pkg/secrets/... ./pkg/oci/... ./pkg/util/yaml/... ./pkg/workspace/... \
    ./pkg/cmd/project/... ./pkg/cmd/resource/... ./pkg/engine/operation/... ./pkg/server/... \
    ./pkg/engine/runtime/terraform/tfops/... ./pkg/backend/...
ok  	kusionstack.io/kusion/pkg/backend	0.831s
ok  	kusionstack.io/kusion/pkg/engine/operation	2.937s
ok  	kusionstack.io/kusion/pkg/server/route	1.506s
... (all ok)

I ran lint only on the packages I changed locally. The full repository lint and the full make cover ran in CI on my fork. For that I added a temporary commit, which is not part of this PR, so that test.yaml runs on push and lint prints every finding (--max-issues-per-linter=0 --max-same-issues=0):

Does this unblock Dependabot? I cherry-picked #1460 (containerd 1.7.36, go 1.25.0) on top of this branch and resolved go.sum with go mod tidy.

Before these changes, the #1460 head failed both jobs (run linked above).

Risks and follow-ups

  • After this merges, the Dependabot PRs need a rebase (@dependabot rebase). They may conflict on go.sum because of the mockey bump. Dependabot resolves that itself.
  • The e2e workflow only gets the Go and action bumps here. The Windows e2e job still fails for an unrelated reason, which CI: Window E2E test failed probabilistically #1237 and a separate PR cover.
  • release.yaml still uses docker/login-action@v2 and goreleaser/goreleaser-action@v4. Their newer majors change defaults (goreleaser v2 needs version: 2 in the config), so I left them for a separate change. A tag push is the only way to test that workflow.
  • check-md-links.yaml uses actions/checkout@master. I did not touch it.
  • make lint installs v2.14.0 only if no golangci-lint is on PATH. Contributors with a v1 binary installed need to upgrade it.

Which issue(s) this PR fixes:

Fixes #1462

Special notes for your reviewer:

@ruquanzhao, could you take a look when you have time? This is three commits, kept separate so they are easy to review: the CI and Go version change, the mockey bump, and the mechanical lint fixes. Once it is merged, the Dependabot PRs above need @dependabot rebase.

Does this PR introduce a user-facing change?

NONE

Additional documentation e.g., design docs, usage docs, etc.:


…ates

Recent dependency updates need Go >= 1.25, but CI is pinned to Go 1.22
and golangci-lint v1.56.2. golangci-lint v1 cannot load a module that
targets Go 1.25 and fails with "unsupported version: 2" typecheck errors.

- Set up Go 1.26 in the test, e2e and release workflows, and build the
  Docker image with golang:1.26.
- Move to golangci-lint v2.14.0 with golangci-lint-action@v9 and migrate
  .golangci.yml to the v2 format, keeping the v1 rule set.
- Update actions/checkout, actions/setup-go and actions/setup-node to v7
  (Node 24 runtime) and actions-goveralls to v1.11.2.
- Fix the go vet findings that newer Go toolchains report: slog calls
  with a missing key, and non-constant format strings.

Co-authored-by: Iamrushabhshahh <rushabhshah842@gmail.com>
Signed-off-by: Iamrushabhshahh <rushabhshah842@gmail.com>
mockey v1.2.10 links to runtime.sysAllocOS through go:linkname. Go 1.23
and later reject that at link time, so every test binary that imports
mockey fails with:

  link: github.com/bytedance/mockey/internal/monkey/common: invalid
  reference to runtime.sysAllocOS

mockey v1.4.6 supports Go 1.26 and still declares go 1.18, so the go
directive in go.mod is unchanged.

Co-authored-by: Iamrushabhshahh <rushabhshah842@gmail.com>
Signed-off-by: Iamrushabhshahh <rushabhshah842@gmail.com>
golangci-lint v2 ships newer staticcheck, predeclared, prealloc and
gofumpt versions. Fix what they now report:

- staticcheck SA1006: use errors.New for constant messages in the
  secrets providers.
- staticcheck S1009: drop nil checks that len() already covers.
- predeclared: rename local "max" variables, which shadow the Go 1.21
  builtin, to "maxItems".
- prealloc: preallocate slices in non-test code, and skip prealloc in
  _test.go files.
- gofumpt: reformat k8s_printer.go.
- Match third_party, builtin and examples directories the way v1 did.

Co-authored-by: Iamrushabhshahh <rushabhshah842@gmail.com>
Signed-off-by: Iamrushabhshahh <rushabhshah842@gmail.com>
@Iamrushabhshahh

Copy link
Copy Markdown
Contributor Author

Note for review: e2e test on windows is expected to fail on this PR. It has failed on main every night since September 2025, for reasons unrelated to this change. The fix for that is in #1464. The two PRs touch different lines of e2e-test.yml and merge cleanly together.

@elliotxx

Copy link
Copy Markdown
Contributor

Thanks so much for this, it's a really thorough migration, and I verified locally that it builds and tests the same on 1.22 and 1.26. To keep review and rollback simple, could we split it into two PRs? First, just the golangci-lint v2 migration on the current Go version. Then a smaller Go bump separately, to 1.25, which the new dependencies need. Also, the Makefile only installs the linter if none is found, so a local v1 would be picked up; a version check there would help. Happy to review both quickly!

@Iamrushabhshahh

Copy link
Copy Markdown
Contributor Author

Hi @elliotxx, thanks for testing it on both versions. That makes sense. I've split it:

  1. ci: migrate golangci-lint to v2 #1466: golangci-lint v2 only, CI stays on Go 1.22, plus a version check in the Makefile so a local v1 isn't picked up.
  2. The Go 1.25 bump (CI, Dockerfile, mockey) as a smaller PR on top, once ci: migrate golangci-lint to v2 #1466 is in.

I'll close this one in favour of those two.

@github-actions github-actions Bot locked and limited conversation to collaborators Oct 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI pinned to Go 1.22 and golangci-lint v1.56.2 blocks all Go dependency updates

2 participants