Skip to content

Make controller-gen/kustomize/golangci-lint/envtest tool-binary caching reliable - #2367

Open
kaovilai wants to merge 5 commits into
openshift:oadp-devfrom
kaovilai:fix-tool-version-check-oadp-dev
Open

Make controller-gen/kustomize/golangci-lint/envtest tool-binary caching reliable#2367
kaovilai wants to merge 5 commits into
openshift:oadp-devfrom
kaovilai:fix-tool-version-check-oadp-dev

Conversation

@kaovilai

@kaovilai kaovilai commented Aug 10, 2026

Copy link
Copy Markdown
Member

Folds in #2152 (same author, same theme, reviewer bandwidth is thin — consolidating into one PR rather than two small ones).

Summary

go-install-tool-branch only installs a build tool (controller-gen, kustomize, golangci-lint) when the binary is missing, never verifying the pinned version against what's already on disk. Once a binary lands at bin/<branch>/<tool>, it's reused forever — even across branch switches, even after CONTROLLER_TOOLS_VERSION/KUSTOMIZE_VERSION/GOLANGCI_LINT_VERSION change — because bin/ is gitignored and nothing else resets it. kustomize/controller-gen targets also weren't fully .PHONY, so Make's own mtime-based staleness check could skip the recipe entirely before any check even ran.

None of the four tools (controller-gen/kustomize/golangci-lint/setup-envtest, the last folded in from #2152) were checked for architecture compatibility either. This repo's own Makefile documents running make test inside docker run --platform linux/amd64 -v $PWD:$PWD ... to reproduce Prow's CI environment — that bind-mounts the real host directory into a forced-arch container, so any go install in there writes a foreign-arch binary straight onto the host's bin/ tree (same path, not a copy). Not envtest-specific — any of these four cached tool binaries can be clobbered this way.

Design

go-install-tool-versioned reads a binary's embedded build info via go version -m — the module version and GOOS/GOARCH a binary was built with — instead of executing it or trusting a sidecar marker file:

$ go version -m bin/oadp-dev/kustomize
	mod	sigs.k8s.io/kustomize/kustomize/v5	v5.2.1	h1:...
	build	GOARCH=arm64
	build	GOOS=darwin
  • Version: the mod line's version, not the tool's own --version output. That output isn't a reliable version or health signal on its own: it can depend on ldflags a tool's own release process sets (which go install doesn't set — verified in practice: three bin/*/kustomize binaries on this machine, installed identically, reported (devel), an unexpanded $Format:%H$ placeholder, and a correct v5.2.1), and some tools exit nonzero on --version even when perfectly healthy (kustomize exits 1, setup-envtest's --help exits 2). A bare probe command that exits nonzero is also dangerous under this Makefile's .SHELLFLAGS = -ec (enables set -e, honored by GNU Make 3.82+): it aborts the whole recipe unless wrapped in an if/|| guard.
  • Architecture: the build GOOS=.../GOARCH=... lines against this host's own, instead of executing the binary and interpreting its exit code — which can't detect a wrong-arch binary at all inside a container with qemu-user-static/binfmt_misc registered (standard for multi-arch CI/build images), since the foreign-arch binary just runs under emulation and returns its own exit code rather than an exec-format-error.

Reading embedded build info sidesteps both problems at once: no execution means no exit-code heuristic to get wrong and no qemu blind spot. setup-envtest's arch check is folded into this same shared macro rather than kept as a separate mechanism.

Known limitations

Testing

Verified against GNU Make 4.4.1 specifically (installed via Homebrew), not just the macOS-default Make 3.81 — the latter silently ignores .SHELLFLAGS, so it can't exercise set -e recipe behavior at all.

  • rm -rf bin/oadp-dev then gmake controller-gen / gmake kustomize / gmake golangci-lint / gmake envtest individually — each installs fresh.
  • Re-running each immediately after → "already installed", no reinstall, no network call.
  • Simulated a wrong-arch binary for all four tools (cross-compiled a real linux/amd64 binary, copied it over the working native binary) → each correctly detected via go version -m's GOARCH/GOOS fields, removed, reinstalled with a valid native binary.
  • gmake generate manifests bundle → zero diff against a clean checkout.
  • gmake test → exit 0, all packages pass, api is up to date, bundle is up to date.
  • coderabbit review --agent → 0 findings.

Cherry-picked to oadp-1.4 as #2368.

Note

Responses generated with Claude

Summary by CodeRabbit

  • Bug Fixes
    • Improved tool installation validation using embedded version and architecture metadata.
    • Automatically removes mismatched binaries and installs the requested versions.
    • Preserves unversioned command aliases where applicable.
    • Removed reliance on separate version marker files.

go-install-tool-branch only installs when the binary is missing, never
verifying the pinned version against what's already on disk. Once a
binary lands in bin/<branch>/, it's reused forever across branch
switches and version bumps since bin/ is gitignored and nothing else
resets it. kustomize and controller-gen's targets also weren't fully
.PHONY (only the wrapper name was, not the binary path), so Make's own
mtime-based staleness check could skip their recipe entirely before
any version check ran.

Introduce go-install-tool-versioned, which compares a sidecar
<binary>.version marker file against the pinned version instead of
introspecting the binary's own --version output. Binary introspection
isn't reliable for every tool installed this way: kustomize's `version`
command depends on ldflags its own release process sets, which `go
install` doesn't set, so identically-installed kustomize binaries were
observed reporting "(devel)", an unexpanded `$Format:%H$` git-archive
placeholder, or a correct version string depending on unrelated
build-time factors.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2f611040-fbf0-41c6-87f0-b8d2c688db59

📥 Commits

Reviewing files that changed from the base of the PR and between b86c89e and a87381b.

📒 Files selected for processing (1)
  • Makefile
🚧 Files skipped from review as they are similar to previous changes (1)
  • Makefile

Walkthrough

The Makefile validates pinned tool versions and host architecture from Go binary metadata. It reinstalls mismatched binaries, preserves applicable unversioned symlinks, and removes sidecar marker handling.

Changes

Tool installation

Layer / File(s) Summary
Build-metadata-aware installer
Makefile
Adds go-install-tool-versioned. The helper checks embedded module version, GOOS, and GOARCH with go version -m, then reuses or reinstalls the binary.
Tool target integration
Makefile
Updates GolangCI-Lint, Kustomize, Controller-Gen, and envtest to use the new installer. Kustomize and Controller-Gen preserve unversioned symlinks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: mpryc, shubham-pampattiwar

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: reliable caching for the four build tools.
Description check ✅ Passed The description explains why the changes were made and provides detailed testing steps and results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The pull-request diff changes only Makefile and contains no test files or Ginkgo title calls, so it introduces no unstable test names.
Test Structure And Quality ✅ Passed The PR changes only Makefile; the exact diff contains no Ginkgo or test code, so these test-structure requirements are not applicable.
Microshift Test Compatibility ✅ Passed The pull-request diff changes only Makefile; it adds no Ginkgo e2e tests or test references to MicroShift-unsupported APIs or features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The PR range changes only Makefile; it adds no Ginkgo e2e tests or test declarations, so it introduces no SNO multi-node assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed The PR changes only Makefile tool-installation logic; the verified diff adds no deployment, controller, manifest, or Kubernetes scheduling constraints.
Ote Binary Stdout Contract ✅ Passed The PR-wide diff changes only Makefile; no Go or Ginkgo process-level code changed, so it introduces no OTE binary stdout write.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The PR changes only Makefile; the verified diff adds no Ginkgo tests or e2e test files, so this compatibility check does not apply.
No-Weak-Crypto ✅ Passed The PR changes only Makefile tool-install logic and comments; the diff introduces no MD5, SHA1, DES, RC4, Blowfish, ECB, custom crypto, or secret comparisons.
Container-Privileges ✅ Passed The PR changes only Makefile; added lines contain no privileged, host namespace, SYS_ADMIN, or allowPrivilegeEscalation settings, and no container manifest changed.
No-Sensitive-Data-In-Logs ✅ Passed The cumulative diff changes only Makefile; build info is captured with stderr suppressed, and new logs contain only tool names and pinned versions, with no passwords, tokens, PII, or customer data.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Makefile`:
- Around line 530-550: Update the versions target to read the .version sidecar
for marker-managed tools such as KUSTOMIZE instead of invoking the binary’s
version command. Preserve existing binary introspection for tools without marker
files, and reuse the marker path established by go-install-tool-versioned.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e93a7bca-24c0-4d5d-82da-82c240a84ce2

📥 Commits

Reviewing files that changed from the base of the PR and between 7a3c9b0 and 8802555.

📒 Files selected for processing (1)
  • Makefile

Comment thread Makefile Outdated
Folds in the fix from openshift#2152 (same author, same theme: harden
Makefile tool-binary caching under bin/). A containerized Make target
(e.g. podman/docker build with a different GOARCH) can write a linux
binary into the shared bin/ directory, replacing the native host
binary `make test` needs — and since setup-envtest isn't
branch-scoped like the other three tools, that binary is shared
across every branch checkout too.

openshift#2152's own check used `$(ENVTEST) --help`'s exit code as the
"is this binary compatible" signal, but that's unreliable the same
way relying on kustomize's --version output was: setup-envtest's own
--help exits 2 by its own convention even on a perfectly good binary,
so that check would have triggered a reinstall on every single
invocation, permanently defeating the cache. Verified by
cross-compiling a real linux/amd64 setup-envtest and running it on
this darwin/arm64 host: the shell reports exit code 126 specifically
(POSIX "found but cannot execute" / exec format error) — check that
instead of any nonzero exit.

Also added $(ENVTEST) to the .PHONY line, matching the fix already
applied to controller-gen/kustomize in the previous commit: without
it, Make's own mtime-based staleness check can skip the recipe (and
therefore this check) entirely once the binary file exists.

Closes openshift#2152

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
@kaovilai kaovilai changed the title Make controller-gen/kustomize/golangci-lint version checks reliable Make controller-gen/kustomize/golangci-lint/envtest tool-binary caching reliable Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Makefile (1)

544-566: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make marker-managed caches platform-specific.

go-install-tool-versioned checks only the version marker. The cache paths for Controller-Gen, Kustomize, and GolangCI-Lint omit GOOS and GOARCH. A same-version binary from another architecture can be reused and fail at execution. Include the platform in the cache key or validate the binary before reuse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 544 - 566, The go-install-tool-versioned cache
currently reuses markers based only on tool version, allowing binaries from
another platform to be selected. Update go-install-tool-versioned and the
Controller-Gen, Kustomize, and GolangCI-Lint cache paths or marker validation to
include GOOS and GOARCH, ensuring reuse only occurs for the current platform
while preserving version checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Makefile`:
- Around line 544-566: The go-install-tool-versioned cache currently reuses
markers based only on tool version, allowing binaries from another platform to
be selected. Update go-install-tool-versioned and the Controller-Gen, Kustomize,
and GolangCI-Lint cache paths or marker validation to include GOOS and GOARCH,
ensuring reuse only occurs for the current platform while preserving version
checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5cedc214-40c1-438d-a936-5286a91d2bf1

📥 Commits

Reviewing files that changed from the base of the PR and between 8802555 and a70d9a8.

📒 Files selected for processing (1)
  • Makefile

@kaovilai

Copy link
Copy Markdown
Member Author

/test 5.0-e2e-test-aws

kaovilai and others added 2 commits August 12, 2026 09:51
The version-marker check alone isn't enough: a binary can have a
correct .version marker but still be the wrong architecture, e.g. if
a containerized build with a different GOARCH bind-mounts the host's
bin/ directory (this Makefile documents exactly that workflow for
`make test`: `docker run --platform linux/amd64 -v $PWD:$PWD ...`).
Anything that does `go install` in there writes onto the host's real
bin/ tree since it's the same mounted path, not a copy. That's not
envtest-specific — it can happen to any of these four cached tool
binaries.

go-install-tool-versioned now also probes `$(1) --version` and checks
specifically for exit code 126 (POSIX "found but cannot execute" /
exec format error), same technique as the envtest fix. Verified all
three tools (controller-gen, kustomize, golangci-lint) correctly
detect and repair a wrong-arch binary even when its .version marker
already matches the pinned version: cross-compiled a real linux/amd64
binary for each, copied it over the working native binary, confirmed
each was detected and replaced.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
An independent second-opinion review caught a severe bug in the
previous commit: the exit-126 probe (`$(1) --version`, then a
separate `if [ $? -eq 126 ]`) is a bare command outside any &&/||/if
guard. Under `set -e` — which this Makefile's `.SHELLFLAGS = -ec`
enables, honored by GNU Make 3.82+ — a bare failing command aborts
the whole recipe immediately, before the exit-code check ever runs.
My local macOS Make (3.81) silently ignores .SHELLFLAGS, so none of
my testing could have caught this.

Confirmed for real: installed GNU Make 4.4.1 via Homebrew and
reproduced the crash directly — `make kustomize` died with
"Error 1" every time the binary already existed, because kustomize's
own `--version` exits 1 by its own convention even on a perfectly
healthy binary (same root cause class as the setup-envtest --help
issue already fixed: a tool's own nonzero-on-success exit code was
being misread as "broken").

Replaced the whole exit-code-probe approach with `go version -m`,
which reads a binary's embedded module version and GOOS/GOARCH
directly from its build info, without executing it at all. This is
strictly better, not just a patch:
  - No execution means no exit-code heuristic to get wrong, and no
    -e hazard, for any of these four tools.
  - No execution also closes a gap the review surfaced: exit-126
    detection cannot work at all inside a container with
    qemu-user-static/binfmt_misc registered (standard in multi-arch
    CI/build images), since a wrong-arch binary just runs under
    emulation and returns its own exit code instead of an
    exec-format-error — defeating the check silently in exactly the
    environments it was meant to protect.
  - Drops the sidecar `.version` marker file entirely — go version -m
    reads the real, authoritative module version already embedded in
    the binary, so there's nothing separate left to go stale or
    desync from a copied/moved binary.

envtest's bespoke arch-only check (added in the previous commit) is
replaced outright by a plain call to the same go-install-tool-versioned
macro used by the other three tools, rather than patched in place —
one verified mechanism instead of two.

Verified end-to-end with GNU Make 4.4.1 specifically (not just the
macOS-default 3.81, which cannot exercise this class of bug): fresh
install, idempotent re-run (previously the exact crash case for
kustomize/envtest), and wrong-arch detection+repair, for all four
tools. `make generate manifests bundle` zero diff, `make test` fully
green.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
sseago
sseago previously approved these changes Aug 12, 2026
Both go-install-tool-versioned's doc comment and the envtest section's
note narrated the debugging process that led here (earlier attempts,
"confirmed with real GNU Make 4.4.1", "the previous exit-code-126
version... reliably crashed", a specific past PR reference) rather
than just stating why the current code is shaped this way. Keeps the
durable WHY (unreliable tool self-report, the set -e hazard as a
standing fact about this Makefile, the qemu/binfmt_misc blind spot)
as present-tense design rationale instead.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
@openshift-ci

openshift-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: kaovilai, shubham-pampattiwar

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [kaovilai,shubham-pampattiwar]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci

openshift-ci Bot commented Aug 13, 2026

Copy link
Copy Markdown

@kaovilai: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@kaovilai
kaovilai requested a review from sseago August 13, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants