Skip to content

Feat/bazel#34

Merged
KevinGruber2001 merged 3 commits intomainfrom
feat/bazel
Mar 24, 2026
Merged

Feat/bazel#34
KevinGruber2001 merged 3 commits intomainfrom
feat/bazel

Conversation

@KevinGruber2001
Copy link
Copy Markdown
Collaborator

@KevinGruber2001 KevinGruber2001 commented Mar 24, 2026

Summary by CodeRabbit

  • New Features

    • Added Bazel remote cache support with separate AC and CAS endpoints
    • Configurable cache settings: max entry size and optional CAS hash verification
    • New cache metrics: hits, misses, hash mismatches, and entry size tracking
  • Bug Fixes

    • Enforced SHA-256 hash validation and upload size limits with clear HTTP error responses
  • Chores

    • Updated Helm chart version to 0.4.0

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

Chart Preview Ready

helm install test oci://ghcr.io/eduide/charts/theia-shared-cache --version 0.4.0-pr.34

Updated: e8c1644

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Adds Bazel remote cache support (AC and CAS) with GET/PUT handlers, SHA-256 validation, CAS hash verification toggle, OpenTelemetry metrics, config + defaults for cache behavior, and Helm chart bump to 0.4.0 including values for cache settings.

Changes

Cohort / File(s) Summary
Helm chart & values
chart/Chart.yaml, chart/templates/configmap.yaml, chart/values.yaml
Bumped chart version to 0.4.0. ConfigMap template now renders max_entry_size_mb from Helm values and adds verify_cas_hash. New top-level cache values: maxEntrySizeMB: 100, verifyCASHash: true.
Runtime configuration
src/configs/config.yaml, src/internal/config/config.go
Added cache.verify_cas_hash: true to default YAML and CacheConfig.VerifyCASHash bool with default true in config loader.
Bazel handler & metrics
src/internal/handler/bazel_handler.go, src/internal/handler/bazel_metrics.go, src/internal/handler/bazel_get.go, src/internal/handler/bazel_put.go
New BazelHandler with separate namespaced AC/CAS storage, constructor NewBazelHandler, OpenTelemetry instruments (hits, misses, hash mismatches, entry size), and GET/PUT implementations including size enforcement, streaming/spooling, CAS hash verification, and metric recording.
Validation & server routing
src/internal/handler/validation.go, src/internal/server/server.go
Added SHA-256 hex validator. Replaced generic /cache group with /gradle and /bazel route groups; Bazel endpoints registered for ac/:hash and cas/:hash. Server startup ensures storage implements storage.NamespacedStorage.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as BazelHandler
    participant Storage
    participant Metrics

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Metrics: GET Flow (Retrieval)
    Client->>Server: GET /bazel/{ac|cas}/:hash
    Server->>Server: Validate SHA-256 hex format
    Server->>Storage: Get(ctx, hash)
    alt Cache Hit
        Storage-->>Server: reader, size
        Server->>Metrics: Record cache hit
        Server->>Client: 200 OK + data
    else Cache Miss
        Storage-->>Server: ErrNotFound
        Server->>Metrics: Record cache miss
        Server->>Client: 404 Not Found
    else Error
        Storage-->>Server: error
        Server->>Client: 500 Internal Error
    end
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Client,Metrics: PUT Flow (Storage)
    Client->>Server: PUT /bazel/{ac|cas}/:hash + body
    Server->>Server: Validate SHA-256 hex format
    Server->>Server: Enforce size limits
    Server->>Server: Read request body
    alt CAS with verification
        Server->>Server: Compute SHA-256 of body
        Server->>Server: Compare with provided hash
        alt Mismatch
            Server->>Metrics: Record hash mismatch
            Server->>Client: 400 Bad Request
        end
    end
    Server->>Storage: Put(ctx, hash, data)
    Server->>Metrics: Record entry size
    alt Success
        Server->>Client: 200 OK
    else Size exceeded
        Server->>Client: 413 Payload Too Large
    else Failure
        Server->>Client: 500 Internal Error
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Create cache feature flag #16: Modifies the same Helm configmap template and cache-related Helm values (max_entry_size_mb, verify_cas_hash) — strong template/values overlap.

Poem

🐰 I hopped in with a shiny new patch,

AC and CAS now share the cache,
Hashes checked and metrics play,
Helm bumped up to brighten the day,
A rabbit's cheer for every successful cache!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/bazel' is vague and uses a generic naming pattern that doesn't describe the actual changes made. Use a more descriptive title that explains the main change, such as 'Add Bazel remote cache handler with GET/PUT endpoints and metrics' or 'Implement Bazel cache support with action cache and content-addressable storage endpoints'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bazel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/internal/server/server.go (1)

95-99: Type assertion fails fatally during startup.

The Fatal call prevents graceful degradation if the storage backend doesn't support namespaces. This is acceptable for a required feature, but consider if this should be a startup validation check in main() rather than during route setup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/internal/server/server.go` around lines 95 - 99, The current type
assertion for namespaced storage (s.storage.(storage.NamespacedStorage)) calls
s.logger.Fatal when it fails during route setup; instead, move this validation
to startup (e.g., in main) or convert it to a recoverable error path: perform
the assertion earlier (before server.New or before RegisterRoutes) and return an
error from the initialization function (or disable Bazel endpoints) rather than
calling s.logger.Fatal in the route/setup code; update the code that constructs
the server (the place that instantiates s and calls its route registration) to
handle the returned error and log/exit in main so the check is a startup
validation, referencing the namespaced type storage.NamespacedStorage, the
s.storage field, and the s.logger.Fatal call to locate the change.
src/internal/handler/bazel_metrics.go (1)

39-44: Consider adding unit specification to the histogram.

The entry_size histogram description mentions "bytes" but the metric lacks an explicit unit. OpenTelemetry best practices recommend specifying units for clarity in metric backends.

♻️ Add unit specification
 	entrySize, err := meter.Float64Histogram(
 		"bazel_cache.entry_size",
-		metric.WithDescription("Size of Bazel cache entries in bytes"))
+		metric.WithDescription("Size of Bazel cache entries"),
+		metric.WithUnit("By"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/internal/handler/bazel_metrics.go` around lines 39 - 44, The histogram
for "bazel_cache.entry_size" is missing an explicit unit; update the
Float64Histogram call (where meter.Float64Histogram is invoked for
"bazel_cache.entry_size" with metric.WithDescription) to include a unit option
(use metric.WithUnit(unit.Bytes) from the otel unit package or equivalent) so
the metric description and backend explicitly know the values are in bytes;
ensure you add the necessary import for the unit package.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/internal/handler/bazel_handler.go`:
- Around line 21-34: NewBazelHandler currently accepts negative maxEntrySize
which causes runtime failures; update NewBazelHandler to validate the
maxEntrySize parameter (the constructor for BazelHandler) and return a non-nil
error if maxEntrySize is negative, e.g., check maxEntrySize < 0 at the start of
NewBazelHandler and return a clear error explaining the invalid maxEntrySize
instead of constructing and returning a BazelHandler instance.

In `@src/internal/handler/bazel_put.go`:
- Around line 49-81: The current handler fully buffers uploads into memory
(using limitedReader + io.ReadAll + bytes.NewReader) before calling store.Put;
change it to stream directly to store.Put when verifyHash is false and to
stream-to-temp-file (or spooled buffer) while hashing when verifyHash is true.
Concretely: for the non-verifying path, pass an io.LimitReader (respecting
h.maxEntrySize) or the request body directly to store.Put instead of reading
into data and remove bytes.NewReader usage; for the verifying path, pipe the
request through a hasher (sha256.New) while writing to a temporary file or
spooled buffer (os.CreateTemp or an in-memory spool) so you can compute
computedHex, enforce h.maxEntrySize, record contentLength after write, and then
call store.Put with the temp file reader; keep the existing metrics and error
handling (h.metrics.EntrySize.Record, h.metrics.HashMismatches, h.logger) but
update them to use actual contentLength determined from the stream or temp file,
and ensure you detect oversized uploads by limiting the reader and returning
StatusRequestEntityTooLarge if the limit is exceeded.

---

Nitpick comments:
In `@src/internal/handler/bazel_metrics.go`:
- Around line 39-44: The histogram for "bazel_cache.entry_size" is missing an
explicit unit; update the Float64Histogram call (where meter.Float64Histogram is
invoked for "bazel_cache.entry_size" with metric.WithDescription) to include a
unit option (use metric.WithUnit(unit.Bytes) from the otel unit package or
equivalent) so the metric description and backend explicitly know the values are
in bytes; ensure you add the necessary import for the unit package.

In `@src/internal/server/server.go`:
- Around line 95-99: The current type assertion for namespaced storage
(s.storage.(storage.NamespacedStorage)) calls s.logger.Fatal when it fails
during route setup; instead, move this validation to startup (e.g., in main) or
convert it to a recoverable error path: perform the assertion earlier (before
server.New or before RegisterRoutes) and return an error from the initialization
function (or disable Bazel endpoints) rather than calling s.logger.Fatal in the
route/setup code; update the code that constructs the server (the place that
instantiates s and calls its route registration) to handle the returned error
and log/exit in main so the check is a startup validation, referencing the
namespaced type storage.NamespacedStorage, the s.storage field, and the
s.logger.Fatal call to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f28a846-1863-407f-b130-c4ed4160cc22

📥 Commits

Reviewing files that changed from the base of the PR and between 042f3f9 and 4d591d0.

📒 Files selected for processing (11)
  • chart/Chart.yaml
  • chart/templates/configmap.yaml
  • chart/values.yaml
  • src/configs/config.yaml
  • src/internal/config/config.go
  • src/internal/handler/bazel_get.go
  • src/internal/handler/bazel_handler.go
  • src/internal/handler/bazel_metrics.go
  • src/internal/handler/bazel_put.go
  • src/internal/handler/validation.go
  • src/internal/server/server.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/internal/handler/bazel_put.go (1)

60-71: Metrics may not reflect actual bytes stored when Content-Length is inaccurate.

Line 63 records metrics based on the claimed contentLength before storage completes. If a client sends fewer bytes than declared (connection drop, bug), the metric will overcount. Consider recording metrics based on actual bytes written, similar to the spooling path.

This is low-risk for well-behaved Bazel clients, but could affect observability accuracy.

♻️ Possible improvement: use a counting reader
 if contentLength >= 0 {
 	// Content-Length known: stream directly to storage
-	limited := io.LimitReader(c.Request.Body, contentLength)
-	h.metrics.EntrySize.Record(c.Request.Context(), float64(contentLength), attrs)
+	countingReader := &countingReader{r: io.LimitReader(c.Request.Body, contentLength)}

-	if err := store.Put(c.Request.Context(), hash, limited, contentLength); err != nil {
+	if err := store.Put(c.Request.Context(), hash, countingReader, contentLength); err != nil {
 		h.logger.Error().Err(err).Str("hash", hash).Str("cache_type", cacheType).Msg("failed to store bazel cache entry")
 		c.Status(http.StatusInternalServerError)
 		return
 	}
+	h.metrics.EntrySize.Record(c.Request.Context(), float64(countingReader.n), attrs)
 	c.Status(http.StatusOK)
 	return
 }

You would need a simple counting wrapper:

type countingReader struct {
    r io.Reader
    n int64
}

func (c *countingReader) Read(p []byte) (int, error) {
    n, err := c.r.Read(p)
    c.n += int64(n)
    return n, err
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/internal/handler/bazel_put.go` around lines 60 - 71, The metric is
recorded using the declared contentLength before store.Put completes, which can
overcount if fewer bytes are actually sent; wrap the limited reader in a simple
counting reader (e.g., countingReader that implements io.Reader and tracks bytes
read) and pass that to store.Put instead of limited, then after store.Put
returns (on success or error) record the actual bytes read with
h.metrics.EntrySize.Record(float64(countingReader.n), attrs); update the code
paths that currently use io.LimitReader -> limited to use io.LimitReader wrapped
by your countingReader and ensure you still handle the existing error logging
via h.logger and HTTP response the same way.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/internal/handler/bazel_put.go`:
- Around line 60-71: The metric is recorded using the declared contentLength
before store.Put completes, which can overcount if fewer bytes are actually
sent; wrap the limited reader in a simple counting reader (e.g., countingReader
that implements io.Reader and tracks bytes read) and pass that to store.Put
instead of limited, then after store.Put returns (on success or error) record
the actual bytes read with h.metrics.EntrySize.Record(float64(countingReader.n),
attrs); update the code paths that currently use io.LimitReader -> limited to
use io.LimitReader wrapped by your countingReader and ensure you still handle
the existing error logging via h.logger and HTTP response the same way.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e5bbdff-120e-4dad-b0f1-3d439680cd4d

📥 Commits

Reviewing files that changed from the base of the PR and between 4d591d0 and bd19545.

📒 Files selected for processing (1)
  • src/internal/handler/bazel_put.go

@KevinGruber2001 KevinGruber2001 merged commit 92984f3 into main Mar 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant