Skip to content

CNTRLPLANE-3329: Use GOCACHEPROG for zero-copy EFS build cache - #8576

Merged
celebdor merged 3 commits into
openshift:mainfrom
celebdor:CNTRLPLANE-3329/enable-fuse-overlayfs
May 28, 2026
Merged

CNTRLPLANE-3329: Use GOCACHEPROG for zero-copy EFS build cache#8576
celebdor merged 3 commits into
openshift:mainfrom
celebdor:CNTRLPLANE-3329/enable-fuse-overlayfs

Conversation

@celebdor

@celebdor celebdor commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace fuse-overlayfs with Go 1.24+'s GOCACHEPROG protocol for serving the EFS-backed Go build cache to CI jobs. The previous approach (#8571) required user namespaces (hostUsers: false) which failed because EFS (NFS) does not support idmapped mounts on RHEL 9's kernel 5.14.

gocacheprog is a small Go program (~180 lines, stdlib only) that implements the GOCACHEPROG JSON-over-stdin/stdout protocol:

  • GET: reads from the read-only EFS PVC first, then the local writable directory
  • PUT: writes to the local writable directory only
  • Zero copy: no FUSE, no overlay mounts, just direct filesystem reads from the EFS PVC
  • No special permissions: works with the default restricted-v2 SCC

Changes:

  • contrib/ci/gocacheprog/ — new GOCACHEPROG binary (Go module, stdlib only)
  • Dockerfile.github-actions-runner — builds and installs gocacheprog into the runner image
  • .github/actions/warm-go-cache/action.yaml — sets GOCACHEPROG env var instead of GOCACHE; falls back to default Go cache if gocacheprog binary is not present

Supersedes #8571 (closed — user namespaces + EFS incompatible on this kernel).

Test plan

  • Cold build with GOCACHEPROG: go build ./support/api/ populates writable cache
  • Warm rebuild: 0.28s, all cache hits from writable dir
  • Read-only source (simulates EFS): 0.22s, zero new entries in writable dir
  • go test works with GOCACHEPROG
  • Konflux builds the runner image with gocacheprog
  • Deploy new runner image and verify CI jobs use GOCACHEPROG (no "gocacheprog not found" warning)
  • Compare job durations before/after with contrib/ci/gha-cache-timing.sh

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a lightweight helper binary to serve a read-only Go build cache with a writable overlay for CI.
  • Chores

    • CI action updated to use the helper when available and warn/fallback when absent.
    • CI runner image now builds and installs the helper and ensures it’s included in build context.
  • Tests

    • Added comprehensive tests validating cache operations and concurrency.

Replace fuse-overlayfs with Go 1.24+'s GOCACHEPROG protocol for serving
the EFS-backed Go build cache. fuse-overlayfs required user namespaces
(hostUsers: false) which fails on EFS because NFS does not support
idmapped mounts on RHEL 9's kernel 5.14.

gocacheprog is a small Go program (~180 lines, stdlib only) that
implements the GOCACHEPROG JSON-over-stdin/stdout protocol. GET requests
read from the read-only EFS cache first, then fall back to a writable
local directory. PUT requests write to the local directory only. This
gives zero-copy cache reads with no special SCC, no user namespaces,
and no FUSE — works with the default restricted-v2 SCC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label May 22, 2026
@openshift-ci

openshift-ci Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot

openshift-ci-robot commented May 22, 2026

Copy link
Copy Markdown

@celebdor: This pull request references CNTRLPLANE-3329 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

Replace fuse-overlayfs with Go 1.24+'s GOCACHEPROG protocol for serving the EFS-backed Go build cache to CI jobs. The previous approach (#8571) required user namespaces (hostUsers: false) which failed because EFS (NFS) does not support idmapped mounts on RHEL 9's kernel 5.14.

gocacheprog is a small Go program (~180 lines, stdlib only) that implements the GOCACHEPROG JSON-over-stdin/stdout protocol:

  • GET: reads from the read-only EFS PVC first, then the local writable directory
  • PUT: writes to the local writable directory only
  • Zero copy: no FUSE, no overlay mounts, just direct filesystem reads from the EFS PVC
  • No special permissions: works with the default restricted-v2 SCC

Changes:

  • contrib/ci/gocacheprog/ — new GOCACHEPROG binary (Go module, stdlib only)
  • Dockerfile.github-actions-runner — builds and installs gocacheprog into the runner image
  • .github/actions/warm-go-cache/action.yaml — sets GOCACHEPROG env var instead of GOCACHE; falls back to default Go cache if gocacheprog binary is not present

Supersedes #8571 (closed — user namespaces + EFS incompatible on this kernel).

Test plan

  • Cold build with GOCACHEPROG: go build ./support/api/ populates writable cache
  • Warm rebuild: 0.28s, all cache hits from writable dir
  • Read-only source (simulates EFS): 0.22s, zero new entries in writable dir
  • go test works with GOCACHEPROG
  • Konflux builds the runner image with gocacheprog
  • Deploy new runner image and verify CI jobs use GOCACHEPROG (no "gocacheprog not found" warning)
  • Compare job durations before/after with contrib/ci/gha-cache-timing.sh

🤖 Generated with Claude Code

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-area labels May 22, 2026
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces gocacheprog, a new Go utility that serves as a JSON-protocol cache server for Go build caching. The utility implements get, put, and close commands to manage a two-tier cache: a writable local directory and an optional read-only EFS-backed directory. The binary is built during Docker image construction and integrated into the CI warm-go-cache GitHub Action, which now exports the GOCACHEPROG environment variable instead of the previous GOCACHE-based approach. The action replaces the earlier fuse-overlayfs mounting logic with direct configuration of the cache server endpoints.

Sequence Diagram

sequenceDiagram
  participant Client as Caller Process
  participant Server as gocacheprog Server
  participant WritableDir as Writable Cache
  participant ReadOnlyDir as Read-Only Cache (optional)
  participant Disk as Output Data File

  rect rgba(100, 149, 237, 0.5)
  Note over Client,Disk: GET request (cache read)
  Client->>Server: JSON get request with ActionID
  Server->>WritableDir: lookup action entry
  alt found in writable cache
    WritableDir-->>Server: action entry + metadata
    Server->>Disk: verify output file exists
    Disk-->>Server: file exists
    Server-->>Client: cache hit with DiskPath
  else not in writable, try read-only
    Server->>ReadOnlyDir: lookup action entry
    alt found in read-only cache
      ReadOnlyDir-->>Server: action entry + metadata
      Server->>Disk: verify output file exists
      Disk-->>Server: file exists
      Server-->>Client: cache hit with DiskPath
    else not found
      Server-->>Client: cache miss
    end
  end
  end

  rect rgba(144, 238, 144, 0.5)
  Note over Client,Disk: PUT request (cache write)
  Client->>Server: JSON put request with OutputID + data
  Server->>WritableDir: create directories for ActionID hash
  WritableDir-->>Server: directory ready
  Server->>Disk: write output data file
  Disk-->>Server: file written
  Server->>WritableDir: write action entry (version + IDs + timestamp)
  WritableDir-->>Server: entry written
  Server-->>Client: success with output path
  end
Loading

Possibly related PRs

  • openshift/hypershift#8568: Changes the same Go build-cache wiring; this PR replaces fuse-overlayfs/GOCACHE approach with gocacheprog/GOCACHEPROG.

Suggested reviewers

  • muraee
🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ❓ Inconclusive Custom check requests Ginkgo test code review, but PR adds standard Go tests (testing.T), not Ginkgo framework tests. Clarify if check applies to all Go tests. If yes, PR passes: TempDir auto-cleanup, meaningful error messages, single responsibilities.
✅ Passed checks (13 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: replacing fuse-overlayfs with GOCACHEPROG for EFS build cache.
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 PR adds standard Go tests, not Ginkgo tests. No Ginkgo patterns (It, Describe, Context, When) or imports found. Check is not applicable.
Microshift Test Compatibility ✅ Passed PR adds no new Ginkgo e2e tests. Only infrastructure files and a Go module with standard Go unit tests (not Ginkgo framework tests) are added.
Single Node Openshift (Sno) Test Compatibility ✅ Passed PR does not add Ginkgo e2e tests. Changes include only GitHub Actions config, Dockerfile updates, and Go unit tests using standard testing package, not Ginkgo BDD tests.
Topology-Aware Scheduling Compatibility ✅ Passed PR modifies only CI tooling (GitHub Actions, Dockerfile, Go utilities). No deployment manifests, operators, or controllers are added, so topology-aware scheduling check does not apply.
Ote Binary Stdout Contract ✅ Passed gocacheprog is not an OTE test binary; it implements the GOCACHEPROG caching protocol with all stdout as JSON and errors to stderr. Check does not apply.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR does not add Ginkgo e2e tests; only adds standard Go unit tests for the gocacheprog utility in contrib/ci/gocacheprog/main_test.go.
No-Weak-Crypto ✅ Passed No weak cryptographic algorithms, custom crypto implementations, or non-constant-time secret comparisons detected. Code uses standard non-crypto libraries only.
Container-Privileges ✅ Passed No privileged settings found. Dockerfile uses root only during build, switches to 'runner' non-root user, with no privileged/hostPID/hostNetwork/SYS_ADMIN/allowPrivilegeEscalation directives.
No-Sensitive-Data-In-Logs ✅ Passed No sensitive data exposed in logs. Only JSON errors and cache paths logged; no credentials, tokens, API keys, or PII.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@celebdor

Copy link
Copy Markdown
Collaborator Author

/override ci/prow/e2e-aws
/override ci/prow/e2e-v2-aws
/override ci/prow/e2e-v2-gke
/override ci/prow/e2e-aks
/override ci/prow/e2e-azure-self-managed
/override ci/prow/e2e-kubevirt-aws-ovn-reduced
/override ci/prow/e2e-aws-upgrade-hypershift-operator
/override ci/prow/images
/override ci/prow/okd-scos-images
/override ci/prow/verify-deps
/override ci/prow/security

@openshift-ci

openshift-ci Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

@celebdor: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:

  • ci/prow/security

Only the following failed contexts/checkruns were expected:

  • CodeRabbit
  • ci/prow/e2e-aks
  • ci/prow/e2e-aws
  • ci/prow/e2e-aws-upgrade-hypershift-operator
  • ci/prow/e2e-azure-self-managed
  • ci/prow/e2e-kubevirt-aws-ovn-reduced
  • ci/prow/e2e-v2-aws
  • ci/prow/e2e-v2-gke
  • ci/prow/images
  • ci/prow/okd-scos-images
  • ci/prow/verify-deps
  • pull-ci-openshift-hypershift-main-e2e-aks
  • pull-ci-openshift-hypershift-main-e2e-aws
  • pull-ci-openshift-hypershift-main-e2e-aws-upgrade-hypershift-operator
  • pull-ci-openshift-hypershift-main-e2e-azure-self-managed
  • pull-ci-openshift-hypershift-main-e2e-kubevirt-aws-ovn-reduced
  • pull-ci-openshift-hypershift-main-e2e-v2-aws
  • pull-ci-openshift-hypershift-main-e2e-v2-gke

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.

Details

In response to this:

/override ci/prow/e2e-aws
/override ci/prow/e2e-v2-aws
/override ci/prow/e2e-v2-gke
/override ci/prow/e2e-aks
/override ci/prow/e2e-azure-self-managed
/override ci/prow/e2e-kubevirt-aws-ovn-reduced
/override ci/prow/e2e-aws-upgrade-hypershift-operator
/override ci/prow/images
/override ci/prow/okd-scos-images
/override ci/prow/verify-deps
/override ci/prow/security

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.

@celebdor
celebdor marked this pull request as ready for review May 22, 2026 06:27
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 22, 2026
@celebdor celebdor added the area/ci-tooling Indicates the PR includes changes for CI or tooling label May 22, 2026
@openshift-ci
openshift-ci Bot requested review from enxebre and muraee May 22, 2026 06:27
@celebdor

Copy link
Copy Markdown
Collaborator Author

/override ci/prow/images
/override ci/prow/okd-scos-images
/override ci/prow/verify-deps
/override ci/prow/security

@openshift-ci

openshift-ci Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

@celebdor: Overrode contexts on behalf of celebdor: ci/prow/images, ci/prow/okd-scos-images, ci/prow/security, ci/prow/verify-deps

Details

In response to this:

/override ci/prow/images
/override ci/prow/okd-scos-images
/override ci/prow/verify-deps
/override ci/prow/security

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.

@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: 6

🧹 Nitpick comments (1)
contrib/ci/gocacheprog/main.go (1)

75-80: ⚖️ Poor tradeoff

Consider adding goroutine limit to prevent unbounded concurrency.

Each request spawns a new goroutine without any limit. Under heavy load, this could spawn thousands of goroutines and exhaust system resources.

Consider using a worker pool or semaphore to limit concurrent request handlers.

🤖 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 `@contrib/ci/gocacheprog/main.go` around lines 75 - 80, The current loop spawns
an unbounded goroutine per request (the anonymous goroutine calling
handleRequest(&req, *roDir, *rwDir) and je.Encode) which can exhaust resources;
add a concurrency limiter (e.g., a semaphore channel or fixed worker pool) and
acquire before launching the goroutine and release when the request is fully
processed (after mu.Unlock/je.Encode) so at most N handlers run concurrently.
Locate the anonymous goroutine that calls handleRequest, mu, and je.Encode and
wrap it with semaphore acquire/release (or instead push req into a bounded
worker queue serviced by N worker goroutines) to bound concurrency.
🤖 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 `@contrib/ci/gocacheprog/main.go`:
- Around line 44-181: New functions (actionFile, outputFile, lookup, handleGet,
handlePut) lack unit tests; add tests using the testing package that exercise
path generation, lookup parsing/validation, get/put success and miss/error
paths, and concurrency. Create tests that use t.TempDir() for isolated rw/ro
dirs, construct known IDs (byte slices) to assert actionFile/outputFile outputs,
write valid and malformed action files to test lookup returning expected
response or false, call handlePut with a request to verify files are created and
action entry contents, call handleGet for hit (rw then ro) and miss cases, and
add a concurrency test that launches multiple goroutines calling
handleGet/handlePut to ensure no races (run with t.Parallel and go test -race);
use response struct fields (ID, OutputID, DiskPath, Miss, Err) from the diff to
assert results and cleanup via t.TempDir so no manual teardown is needed.
- Around line 170-175: The size field in the formatted entry uses len(req.Body)
but the protocol's authoritative size is req.BodySize; update the fmt.Sprintf
call that builds entry (the "v1 %s %s %d %d\n" line) to use req.BodySize instead
of len(req.Body), and ensure the format verb matches req.BodySize's type
(convert/cast if necessary) so the size is printed correctly.
- Around line 97-109: actionFile and outputFile can panic when id is empty
because h[:2] slices into an empty string; add a guard at the top of both
functions (actionFile and outputFile) that checks len(id) >= 1 and returns an
empty string (or another sentinel) immediately if not, then proceed to call
hex.EncodeToString and use h[:2]; this prevents the slice bounds panic and keeps
callers able to detect invalid IDs.
- Line 163: The call to os.MkdirAll(filepath.Dir(dPath), 0o777) ignores its
returned error; change this to capture and handle the error (e.g., err :=
os.MkdirAll(...); if err != nil { return fmt.Errorf("creating dir %s: %w",
filepath.Dir(dPath), err) } or log and return) before proceeding to os.WriteFile
so failures are surfaced early; update the surrounding function to propagate or
handle that error accordingly and reference os.MkdirAll, filepath.Dir, dPath and
the subsequent os.WriteFile call when making the change.
- Around line 60-81: The loop in main spawns goroutines that call handleRequest
and encode responses (using je and mu) but returns immediately on io.EOF,
dropping in-flight responses; add a sync.WaitGroup in main, call wg.Add(1)
before launching the goroutine that handles a request, defer wg.Done() inside
that goroutine, and on io.EOF break the loop instead of returning so you can
call wg.Wait() after the loop to ensure all responses are encoded before
exiting; keep using mu to protect je.Encode as before and preserve existing
error handling for jd.Decode and body decoding.
- Line 169: The call to os.MkdirAll(filepath.Dir(aPath), 0o777) currently
ignores its error; update the code around aPath to capture and handle the
returned error from os.MkdirAll (e.g., if err := os.MkdirAll(...); err != nil {
return err / log.Fatal / wrap and return }) before calling os.WriteFile so
failures to create the directory are reported explicitly and avoid confusing
downstream errors.

---

Nitpick comments:
In `@contrib/ci/gocacheprog/main.go`:
- Around line 75-80: The current loop spawns an unbounded goroutine per request
(the anonymous goroutine calling handleRequest(&req, *roDir, *rwDir) and
je.Encode) which can exhaust resources; add a concurrency limiter (e.g., a
semaphore channel or fixed worker pool) and acquire before launching the
goroutine and release when the request is fully processed (after
mu.Unlock/je.Encode) so at most N handlers run concurrently. Locate the
anonymous goroutine that calls handleRequest, mu, and je.Encode and wrap it with
semaphore acquire/release (or instead push req into a bounded worker queue
serviced by N worker goroutines) to bound concurrency.
🪄 Autofix (Beta)

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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 3d230caa-8f1a-4b3b-8fb1-e46570a25056

📥 Commits

Reviewing files that changed from the base of the PR and between d24af10 and 6f58081.

📒 Files selected for processing (4)
  • .github/actions/warm-go-cache/action.yaml
  • Dockerfile.github-actions-runner
  • contrib/ci/gocacheprog/go.mod
  • contrib/ci/gocacheprog/main.go

Comment on lines +44 to +181
func main() {
roDir := flag.String("ro", "", "read-only cache directory (e.g. EFS mount)")
rwDir := flag.String("rw", "", "writable cache directory (e.g. /tmp/go-build-cache)")
flag.Parse()

if *rwDir == "" {
fmt.Fprintln(os.Stderr, "gocacheprog: --rw is required")
os.Exit(1)
}

jd := json.NewDecoder(os.Stdin)
je := json.NewEncoder(os.Stdout)
var mu sync.Mutex

je.Encode(response{KnownCommands: []string{"get", "put", "close"}})

for {
var req request
if err := jd.Decode(&req); err != nil {
if err == io.EOF {
return
}
log.Fatalf("gocacheprog: decode request: %v", err)
}

if req.Command == "put" && req.BodySize > 0 {
if err := jd.Decode(&req.Body); err != nil {
log.Fatalf("gocacheprog: decode body: %v", err)
}
}

go func() {
res := handleRequest(&req, *roDir, *rwDir)
mu.Lock()
je.Encode(res)
mu.Unlock()
}()
}
}

func handleRequest(req *request, roDir, rwDir string) response {
switch req.Command {
case "get":
return handleGet(req, roDir, rwDir)
case "put":
return handlePut(req, rwDir)
case "close":
return response{ID: req.ID}
default:
return response{ID: req.ID, Err: "unknown command"}
}
}

// actionFile returns the path to a Go cache action entry.
// Format: <dir>/<first-byte-hex>/<full-hex-actionID>-a
func actionFile(dir string, id []byte) string {
h := hex.EncodeToString(id)
return filepath.Join(dir, h[:2], h+"-a")
}

// outputFile returns the path to a Go cache data file.
// Format: <dir>/<first-byte-hex>/<full-hex-outputID>-d
func outputFile(dir string, id []byte) string {
h := hex.EncodeToString(id)
return filepath.Join(dir, h[:2], h+"-d")
}

// lookup reads a Go cache action entry and verifies the data file exists.
// The action entry format is: v1 <hexActionID> <hexOutputID> <size> <unixnanos>
func lookup(dir string, actionID []byte) (resp response, ok bool) {
data, err := os.ReadFile(actionFile(dir, actionID))
if err != nil {
return
}
fields := strings.Fields(strings.TrimSpace(string(data)))
if len(fields) != 5 || fields[0] != "v1" {
return
}
if fields[1] != hex.EncodeToString(actionID) {
return
}
outputID, err := hex.DecodeString(fields[2])
if err != nil {
return
}
nanos, err := strconv.ParseInt(fields[4], 10, 64)
if err != nil {
return
}
dPath := outputFile(dir, outputID)
fi, err := os.Stat(dPath)
if err != nil {
return
}
t := time.Unix(0, nanos)
return response{
OutputID: outputID,
Size: fi.Size(),
Time: &t,
DiskPath: dPath,
}, true
}

func handleGet(req *request, roDir, rwDir string) response {
if resp, ok := lookup(rwDir, req.ActionID); ok {
resp.ID = req.ID
return resp
}
if roDir != "" {
if resp, ok := lookup(roDir, req.ActionID); ok {
resp.ID = req.ID
return resp
}
}
return response{ID: req.ID, Miss: true}
}

func handlePut(req *request, rwDir string) response {
dPath := outputFile(rwDir, req.OutputID)
os.MkdirAll(filepath.Dir(dPath), 0o777)
if err := os.WriteFile(dPath, req.Body, 0o666); err != nil {
return response{ID: req.ID, Err: err.Error()}
}

aPath := actionFile(rwDir, req.ActionID)
os.MkdirAll(filepath.Dir(aPath), 0o777)
entry := fmt.Sprintf("v1 %s %s %d %d\n",
hex.EncodeToString(req.ActionID),
hex.EncodeToString(req.OutputID),
len(req.Body),
time.Now().UnixNano(),
)
if err := os.WriteFile(aPath, []byte(entry), 0o666); err != nil {
return response{ID: req.ID, Err: err.Error()}
}

return response{ID: req.ID, DiskPath: dPath}
}

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add unit tests for the new functions.

This file introduces multiple new functions (handleGet, handlePut, lookup, actionFile, outputFile) without accompanying unit tests. As per coding guidelines, unit tests should always be included when creating new functions.

Consider adding tests for:

  • Action/output file path generation with various input IDs
  • Cache lookup with valid/invalid action entries
  • GET requests with cache hit/miss scenarios
  • PUT requests with successful writes and error cases
  • Concurrent request handling

As per coding guidelines, always include unit tests when creating new functions or modifying existing ones.

🤖 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 `@contrib/ci/gocacheprog/main.go` around lines 44 - 181, New functions
(actionFile, outputFile, lookup, handleGet, handlePut) lack unit tests; add
tests using the testing package that exercise path generation, lookup
parsing/validation, get/put success and miss/error paths, and concurrency.
Create tests that use t.TempDir() for isolated rw/ro dirs, construct known IDs
(byte slices) to assert actionFile/outputFile outputs, write valid and malformed
action files to test lookup returning expected response or false, call handlePut
with a request to verify files are created and action entry contents, call
handleGet for hit (rw then ro) and miss cases, and add a concurrency test that
launches multiple goroutines calling handleGet/handlePut to ensure no races (run
with t.Parallel and go test -race); use response struct fields (ID, OutputID,
DiskPath, Miss, Err) from the diff to assert results and cleanup via t.TempDir
so no manual teardown is needed.

Comment thread contrib/ci/gocacheprog/main.go
Comment thread contrib/ci/gocacheprog/main.go
Comment thread contrib/ci/gocacheprog/main.go Outdated
Comment thread contrib/ci/gocacheprog/main.go Outdated
Comment thread contrib/ci/gocacheprog/main.go
@codecov

codecov Bot commented May 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.61%. Comparing base (d24af10) to head (e83c53d).
⚠️ Report is 52 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8576      +/-   ##
==========================================
+ Coverage   40.41%   40.61%   +0.20%     
==========================================
  Files         755      755              
  Lines       93235    93227       -8     
==========================================
+ Hits        37679    37864     +185     
+ Misses      52854    52640     -214     
- Partials     2702     2723      +21     

see 13 files with indirect coverage changes

Flag Coverage Δ
cmd-support 34.70% <ø> (+0.26%) ⬆️
cpo-hostedcontrolplane 41.77% <ø> (+0.01%) ⬆️
cpo-other 41.06% <ø> (+0.75%) ⬆️
hypershift-operator 50.75% <ø> (+0.02%) ⬆️
other 31.58% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bryan-cox bryan-cox left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

GitHub Actions review — 7 findings (6 inline + 1 general).

Not inline (outside diff hunk): Dockerfile.github-actions-runner line 14 still installs fuse-overlayfs via apt-get, but the action.yaml no longer uses it. Remove it from the apt-get install list to reduce image size and attack surface.

Comment on lines +60 to +81
for {
var req request
if err := jd.Decode(&req); err != nil {
if err == io.EOF {
return
}
log.Fatalf("gocacheprog: decode request: %v", err)
}

if req.Command == "put" && req.BodySize > 0 {
if err := jd.Decode(&req.Body); err != nil {
log.Fatalf("gocacheprog: decode body: %v", err)
}
}

go func() {
res := handleRequest(&req, *roDir, *rwDir)
mu.Lock()
je.Encode(res)
mu.Unlock()
}()
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When stdin hits EOF (line 64), main() returns immediately while goroutines spawned here may still be in-flight. Their responses are silently dropped.

Add a sync.WaitGroup to drain in-flight handlers before returning:

var wg sync.WaitGroup
for {
    var req request
    if err := jd.Decode(&req); err != nil {
        if err == io.EOF {
            break
        }
        log.Fatalf("gocacheprog: decode request: %v", err)
    }
    // ... body decode ...
    wg.Add(1)
    go func() {
        defer wg.Done()
        res := handleRequest(&req, *roDir, *rwDir)
        mu.Lock()
        je.Encode(res)
        mu.Unlock()
    }()
}
wg.Wait()

In practice Go may not wait for trailing responses, but this is the correct pattern and prevents data races on os.Stdout during exit.

Comment on lines +69 to +72
if req.Command == "put" && req.BodySize > 0 {
if err := jd.Decode(&req.Body); err != nil {
log.Fatalf("gocacheprog: decode body: %v", err)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The GOCACHEPROG protocol sends the body as raw bytes after the JSON request line, not as a separate JSON value. Using jd.Decode(&req.Body) expects a JSON-encoded token (base64 string). If this has been tested end-to-end and works, Go must be encoding it as JSON — but worth double-checking against the Go 1.24 GOCACHEPROG spec to make sure this isn't silently truncating or corrupting cache entries.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch to double-check. I verified this end-to-end (cold build, warm rebuild, read-only source) and it works correctly.

The Go toolchain sends the body as a JSON-encoded base64 string on a separate line after the request JSON — this is how encoding/json marshals []byte fields. The reference implementation (bradfitz/go-tool-cache) also reads the body with jd.Decode(&req.ObjectID) / jd.Decode(&bodyb) using json.Decoder, which expects JSON tokens (i.e., base64 strings for byte slices). So jd.Decode(&req.Body) correctly decodes the base64 JSON string into []byte.

The key confusion is that the internal spec says "body bytes" but the wire format is JSON-all-the-way — each body is a JSON value (base64 string) on its own line, not raw bytes on the stream.

Comment on lines +97 to +109
// actionFile returns the path to a Go cache action entry.
// Format: <dir>/<first-byte-hex>/<full-hex-actionID>-a
func actionFile(dir string, id []byte) string {
h := hex.EncodeToString(id)
return filepath.Join(dir, h[:2], h+"-a")
}

// outputFile returns the path to a Go cache data file.
// Format: <dir>/<first-byte-hex>/<full-hex-outputID>-d
func outputFile(dir string, id []byte) string {
h := hex.EncodeToString(id)
return filepath.Join(dir, h[:2], h+"-d")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If id is an empty byte slice, hex.EncodeToString(id) returns "" and h[:2] panics with a slice bounds error. Go shouldn't send empty IDs, but for a standalone binary a guard is cheap:

func actionFile(dir string, id []byte) string {
    if len(id) == 0 {
        return ""
    }
    h := hex.EncodeToString(id)
    return filepath.Join(dir, h[:2], h+"-a")
}

Same for outputFile.

Comment thread contrib/ci/gocacheprog/main.go Outdated
Comment on lines +158 to +169
return response{ID: req.ID, Miss: true}
}

func handlePut(req *request, rwDir string) response {
dPath := outputFile(rwDir, req.OutputID)
os.MkdirAll(filepath.Dir(dPath), 0o777)
if err := os.WriteFile(dPath, req.Body, 0o666); err != nil {
return response{ID: req.ID, Err: err.Error()}
}

aPath := actionFile(rwDir, req.ActionID)
os.MkdirAll(filepath.Dir(aPath), 0o777)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Both os.MkdirAll calls (lines 163 and 169) ignore errors. If directory creation fails (permissions, disk full), the subsequent os.WriteFile fails with a confusing "no such file or directory". Check the error:

if err := os.MkdirAll(filepath.Dir(dPath), 0o777); err != nil {
    return response{ID: req.ID, Err: err.Error()}
}

Same for aPath on line 169.

Comment on lines +170 to +175
entry := fmt.Sprintf("v1 %s %s %d %d\n",
hex.EncodeToString(req.ActionID),
hex.EncodeToString(req.OutputID),
len(req.Body),
time.Now().UnixNano(),
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The size in the action entry uses len(req.Body) but the protocol provides req.BodySize as the authoritative value. These could differ if the body was partially read or if there's a type mismatch (int vs int64). Use req.BodySize for consistency:

entry := fmt.Sprintf("v1 %s %s %d %d\n",
    hex.EncodeToString(req.ActionID),
    hex.EncodeToString(req.OutputID),
    req.BodySize,
    time.Now().UnixNano(),
)

using: composite
steps:
- shell: bash
run: |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: mkdir -p /tmp/go-build-cache is only needed when gocacheprog is found (it becomes the --rw dir). In the fallback path (no gocacheprog), the directory is created but never used. Consider moving it inside the if command -v gocacheprog block.

- Add sync.WaitGroup to drain in-flight goroutines before exit
- Guard actionFile/outputFile against empty IDs to prevent panic
- Check errors from os.MkdirAll in handlePut
- Use req.BodySize instead of len(req.Body) for action entry size
- Move mkdir inside gocacheprog availability check in action.yaml
- Remove fuse-overlayfs from Dockerfile (no longer needed)
- Add comprehensive unit tests with race detection coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
contrib/ci/gocacheprog/main.go (1)

61-85: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle close as terminal command and check je.Encode errors in gocacheprog main loop.

  • close only returns a response in handleRequest, but main keeps decoding until io.EOF, which can hang shutdown when stdin stays open.
  • je.Encode(...) errors are ignored both for the initial KnownCommands response and for each request response.
  • Goroutine uses handleRequest(&req, ...) with &req pointing to a loop-reused variable; capture req by value (or copy needed fields) inside the goroutine to avoid races.
Suggested fix
 for {
 	var req request
 	if err := jd.Decode(&req); err != nil {
 		if err == io.EOF {
 			break
 		}
 		log.Fatalf("gocacheprog: decode request: %v", err)
 	}
@@
+	if req.Command == "close" {
+		res := handleRequest(&req, *roDir, *rwDir)
+		mu.Lock()
+		if err := je.Encode(res); err != nil {
+			mu.Unlock()
+			log.Fatalf("gocacheprog: encode response: %v", err)
+		}
+		mu.Unlock()
+		break
+	}
+
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
 		res := handleRequest(&req, *roDir, *rwDir)
 		mu.Lock()
-		je.Encode(res)
+		if err := je.Encode(res); err != nil {
+			mu.Unlock()
+			log.Printf("gocacheprog: encode response: %v", err)
+			return
+		}
 		mu.Unlock()
 	}()
 }
 wg.Wait()
🤖 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 `@contrib/ci/gocacheprog/main.go` around lines 61 - 85, The main loop should
treat the "close" command as terminal, stop decoding further requests and break
the loop immediately when req.Command == "close" (after decoding any body if
needed), so shutdown won't hang; also capture the loop variable by value when
spawning the goroutine (e.g., local copy req := req) to avoid races when calling
handleRequest(&req, ...); check and handle errors returned by je.Encode
everywhere it's called (including the initial KnownCommands response and inside
the goroutine) and log/exit on encode failure; keep the wg usage so outstanding
handlers finish before exit (wg.Wait() after the loop).
🤖 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 `@contrib/ci/gocacheprog/main_test.go`:
- Around line 151-152: The setup calls in the malformed-entry lookup subtests
call os.MkdirAll and os.WriteFile but ignore their returned errors; update those
calls (the ones creating filepath.Dir(aPath) and writing to aPath) to capture
the errors and fail the test on error (e.g., if err := os.MkdirAll(...); err !=
nil { t.Fatalf("MkdirAll failed: %v", err) } and similarly for os.WriteFile),
doing the same for the other occurrences around lines creating bPath/cPath so
test fixtures cannot silently fail.

In `@contrib/ci/gocacheprog/main.go`:
- Line 59: The JSON encoder calls (je.Encode(response{...}) in main.go)
currently ignore returned errors; update both places where je.Encode is called
(the KnownCommands response and the later response at lines ~80-82) to check the
error result, and handle failures by reporting the error (e.g.,
fmt.Fprintln(os.Stderr, err) or processLogger/error logger) and exiting or
returning a non-nil error so the failure isn't silently dropped; ensure you
reference the je.Encode calls and the response struct when making the change.

---

Outside diff comments:
In `@contrib/ci/gocacheprog/main.go`:
- Around line 61-85: The main loop should treat the "close" command as terminal,
stop decoding further requests and break the loop immediately when req.Command
== "close" (after decoding any body if needed), so shutdown won't hang; also
capture the loop variable by value when spawning the goroutine (e.g., local copy
req := req) to avoid races when calling handleRequest(&req, ...); check and
handle errors returned by je.Encode everywhere it's called (including the
initial KnownCommands response and inside the goroutine) and log/exit on encode
failure; keep the wg usage so outstanding handlers finish before exit (wg.Wait()
after the loop).
🪄 Autofix (Beta)

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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: d188264d-86f0-4283-972d-260fd69cdea6

📥 Commits

Reviewing files that changed from the base of the PR and between 6f58081 and 8348e02.

📒 Files selected for processing (4)
  • .github/actions/warm-go-cache/action.yaml
  • Dockerfile.github-actions-runner
  • contrib/ci/gocacheprog/main.go
  • contrib/ci/gocacheprog/main_test.go
💤 Files with no reviewable changes (1)
  • Dockerfile.github-actions-runner
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/actions/warm-go-cache/action.yaml

Comment on lines +151 to +152
os.MkdirAll(filepath.Dir(aPath), 0o777)
os.WriteFile(aPath, []byte("garbage"), 0o666)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check setup I/O errors in malformed-entry lookup subtests.

Line 151, Line 152, Line 164, and Line 170 ignore os.MkdirAll/os.WriteFile errors. That can mask fixture setup failures and produce misleading test outcomes.

Suggested fix
-		os.MkdirAll(filepath.Dir(aPath), 0o777)
-		os.WriteFile(aPath, []byte("garbage"), 0o666)
+		if err := os.MkdirAll(filepath.Dir(aPath), 0o777); err != nil {
+			t.Fatalf("mkdir action dir: %v", err)
+		}
+		if err := os.WriteFile(aPath, []byte("garbage"), 0o666); err != nil {
+			t.Fatalf("write malformed action entry: %v", err)
+		}
@@
-		os.MkdirAll(filepath.Dir(aPath), 0o777)
+		if err := os.MkdirAll(filepath.Dir(aPath), 0o777); err != nil {
+			t.Fatalf("mkdir action dir: %v", err)
+		}
@@
-		os.WriteFile(aPath, []byte(entry), 0o666)
+		if err := os.WriteFile(aPath, []byte(entry), 0o666); err != nil {
+			t.Fatalf("write action entry: %v", err)
+		}

As per coding guidelines, "Always check errors — don't ignore them".

Also applies to: 164-170

🤖 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 `@contrib/ci/gocacheprog/main_test.go` around lines 151 - 152, The setup calls
in the malformed-entry lookup subtests call os.MkdirAll and os.WriteFile but
ignore their returned errors; update those calls (the ones creating
filepath.Dir(aPath) and writing to aPath) to capture the errors and fail the
test on error (e.g., if err := os.MkdirAll(...); err != nil { t.Fatalf("MkdirAll
failed: %v", err) } and similarly for os.WriteFile), doing the same for the
other occurrences around lines creating bPath/cPath so test fixtures cannot
silently fail.

var mu sync.Mutex
var wg sync.WaitGroup

je.Encode(response{KnownCommands: []string{"get", "put", "close"}})

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check and handle JSON encoder errors.

Both je.Encode(...) calls ignore errors. If stdout write fails, responses are silently lost and debugging becomes difficult.

As per coding guidelines, "Always check errors — don't ignore them".

Also applies to: 80-82

🤖 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 `@contrib/ci/gocacheprog/main.go` at line 59, The JSON encoder calls
(je.Encode(response{...}) in main.go) currently ignore returned errors; update
both places where je.Encode is called (the KnownCommands response and the later
response at lines ~80-82) to check the error result, and handle failures by
reporting the error (e.g., fmt.Fprintln(os.Stderr, err) or processLogger/error
logger) and exiting or returning a non-nil error so the failure isn't silently
dropped; ensure you reference the je.Encode calls and the response struct when
making the change.

@bryan-cox

Copy link
Copy Markdown
Member

/approve

@openshift-ci

openshift-ci Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox, celebdor

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:

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 added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label May 27, 2026
@hypershift-jira-solve-ci

hypershift-jira-solve-ci Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

I now have the complete picture. Here is the analysis:

Test Failure Analysis Complete

Job Information

  • Prow Job: Red Hat Konflux / hypershift-gh-actions-runner-on-pull-request
  • Build ID: hypershift-gh-actions-runner-on-pull-request-64h22
  • Pipeline: Konflux (Tekton) — crt-redhat-acm-tenant namespace
  • PR: CNTRLPLANE-3329: Use GOCACHEPROG for zero-copy EFS build cache #8576CNTRLPLANE-3329: Use GOCACHEPROG for zero-copy EFS build cache
  • Failed Task: build-images (container image build for Dockerfile.github-actions-runner)
  • Duration: ~5 minutes (15:08:54 → 15:22:06 UTC)

Test Failure Analysis

Error

Error: building at STEP "COPY contrib/ci/gocacheprog/ /tmp/gocacheprog/":
  no items matching glob "/var/workdir/source/contrib/ci/gocacheprog" copied
  (1 filtered out using /tmp/.dockerignore.AY46gM): no such file or directory

Summary

The Konflux build-images task fails because the Dockerfile.github-actions-runner added by this PR tries to COPY contrib/ci/gocacheprog/ into the image, but the repository's .dockerignore file contains a blanket contrib/ exclusion rule on line 3. The Docker build context strips the entire contrib/ directory before the COPY instruction ever runs, so the build engine reports the path as filtered out and the step fails immediately. The PR adds new source files under contrib/ci/gocacheprog/ and adds a Dockerfile COPY for them, but does not update .dockerignore to allow that specific subdirectory through.

Root Cause

The root cause is a conflict between the .dockerignore file and the new COPY instruction in Dockerfile.github-actions-runner.

The .dockerignore file at the repository root contains:

bin/
hack/tools/bin/
contrib/          ← line 3: excludes the ENTIRE contrib/ tree
.github/
.tekton/
.ci-operator.yaml
.ko.yaml

The PR adds this line to Dockerfile.github-actions-runner:

COPY contrib/ci/gocacheprog/ /tmp/gocacheprog/

When the Konflux pipeline builds the container image, the Docker daemon (or Buildah) first applies .dockerignore to the build context, which strips contrib/ entirely. By the time the COPY instruction executes, the contrib/ci/gocacheprog/ directory simply does not exist in the build context. The error message confirms this: (1 filtered out using /tmp/.dockerignore.AY46gM).

This is a straightforward omission — the PR adds a new dependency on contrib/ci/gocacheprog/ being in the build context but does not add a negation rule to .dockerignore to exempt it.

Recommendations
  1. Add an exception to .dockerignore — Add a negation rule to allow contrib/ci/gocacheprog/ through while keeping the rest of contrib/ excluded:

    contrib/
    !contrib/ci/gocacheprog/
    

    Note: Docker's .dockerignore requires parent directories to be un-excluded first for nested negations to work. If the above doesn't work, use:

    contrib/
    !contrib/ci/
    contrib/ci/*
    !contrib/ci/gocacheprog/
    
  2. Alternative: move the gocacheprog source — If modifying .dockerignore is undesirable (e.g., other Dockerfiles should not see contrib/), consider moving the gocacheprog source to a location not covered by .dockerignore, such as hack/gocacheprog/ or cmd/gocacheprog/.

  3. Verify other Dockerfiles — The .dockerignore applies to all Docker builds in the repo. Ensure the negation rule does not unintentionally expose contrib/ci/gocacheprog/ to Dockerfiles that don't need it (this is generally harmless since unused files just increase context size slightly).

Evidence
Evidence Detail
Failed task build-images in Konflux pipeline hypershift-gh-actions-runner-on-pull-request-64h22
Error message no items matching glob "/var/workdir/source/contrib/ci/gocacheprog" copied (1 filtered out using /tmp/.dockerignore.AY46gM)
.dockerignore line 3 contrib/ — blanket exclusion of entire contrib directory
PR adds COPY instruction COPY contrib/ci/gocacheprog/ /tmp/gocacheprog/ in Dockerfile.github-actions-runner
PR adds source files contrib/ci/gocacheprog/go.mod, contrib/ci/gocacheprog/main.go, contrib/ci/gocacheprog/main_test.go
.dockerignore not modified PR does not touch .dockerignore — no negation rule added
Konflux details URL PipelineRun logs

The contrib/ directory is excluded by .dockerignore but the runner
Dockerfile needs contrib/ci/gocacheprog/ in its build context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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.

🧹 Nitpick comments (1)
.dockerignore (1)

4-4: ⚡ Quick win

Use an explicit recursive un-ignore for subtree contents.

!contrib/ci/gocacheprog/ may only re-include the directory entry; add an explicit recursive exception so all files are guaranteed to be in build context.

Suggested patch
 contrib/
 !contrib/ci/gocacheprog/
+!contrib/ci/gocacheprog/**
🤖 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 @.dockerignore at line 4, The .dockerignore currently un-ignores only the
contrib/ci/gocacheprog/ directory entry which may not include its files; update
the pattern to explicitly recurse by adding an exception like
"!contrib/ci/gocacheprog/**" (keep or replace the existing
"!contrib/ci/gocacheprog/" line) so all files under contrib/ci/gocacheprog are
included in the Docker build context.
🤖 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.

Nitpick comments:
In @.dockerignore:
- Line 4: The .dockerignore currently un-ignores only the
contrib/ci/gocacheprog/ directory entry which may not include its files; update
the pattern to explicitly recurse by adding an exception like
"!contrib/ci/gocacheprog/**" (keep or replace the existing
"!contrib/ci/gocacheprog/" line) so all files under contrib/ci/gocacheprog are
included in the Docker build context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: db76e4b6-c5cf-4fa0-81eb-5eca343ed2f6

📥 Commits

Reviewing files that changed from the base of the PR and between 8348e02 and e83c53d.

📒 Files selected for processing (1)
  • .dockerignore

@openshift-ci

openshift-ci Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

@celebdor: 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.

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. area/ci-tooling Indicates the PR includes changes for CI or tooling jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants