Skip to content

feat(config): add oci:// prefix, multi-cloud auth, and digest caching to OCI extends#253

Merged
skevetter merged 1 commit into
mainfrom
3ae9/oci-extends-enhancements
May 7, 2026
Merged

feat(config): add oci:// prefix, multi-cloud auth, and digest caching to OCI extends#253
skevetter merged 1 commit into
mainfrom
3ae9/oci-extends-enhancements

Conversation

@skevetter

@skevetter skevetter commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds three tightly-coupled enhancements to OCI extends resolution:

  • oci:// prefix support: isOCIRef() now recognizes the oci:// URI scheme and resolveOCIExtends strips the prefix before pulling, matching devcontainer spec behavior.
  • Multi-cloud authentication: Replaces bare authn.DefaultKeychain with image.GetKeychain(ctx) which provides k8s in-cluster auth, AWS ECR, GCR, Azure ACR, and Docker config.json in priority order. Falls back to DefaultKeychain on error.
  • Digest-based caching: Caches fetched devcontainer.json alongside a digest sidecar file. On subsequent resolves, performs a lightweight HEAD request to compare digests — only re-pulls when the manifest has changed.

Context threading (context.Context) is wired through resolveExtendsArrayresolveExtendsSingleresolveOCIExtendspullOCIExtendsJSON to support the auth keychain.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for optional oci:// prefix in OCI references with automatic normalization.
    • Implemented caching for OCI artifact resolution to improve performance and reduce redundant fetches.
  • Refactor

    • Enhanced context propagation throughout extends resolution pipeline for improved cancellation and timeout handling.
  • Tests

    • Expanded test coverage for OCI reference formats and cache validation scenarios.

… to OCI extends

Support the oci:// URI scheme in extends references, replace the bare
DefaultKeychain with image.GetKeychain for multi-cloud authentication
(AWS ECR, GCR, ACR, k8s in-cluster), and introduce SHA-256 digest-based
caching that skips full pulls when the remote manifest is unchanged.
@netlify

netlify Bot commented May 6, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit 901b5b0
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/69fbba142179e100087c1184

@skevetter skevetter changed the title feat(config): enhance OCI extends with oci:// prefix, auth, and digest caching feat(config): add oci:// prefix, multi-cloud auth, and digest caching to OCI extends May 6, 2026
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR threads context.Context throughout the devcontainer extends resolution pipeline and adds OCI-specific enhancements: detection and normalization of oci:// prefixed references, context-driven keychain selection, and digest-based disk caching for fetched OCI artifacts.

Changes

Extends Resolution with Context and OCI Caching

Layer / File(s) Summary
API Signature Update
pkg/devcontainer/config/extends.go
resolveExtendsArray, resolveExtendsSingle, and parseDevContainerJSONFileWithVisited updated to accept context.Context as first parameter; context is threaded through the entire call chain.
OCI Reference Handling
pkg/devcontainer/config/extends_oci.go (lines 37–55)
isOCIRef now recognizes oci:// prefix; stripOCIPrefix normalizes references by removing the scheme before cache/cycle tracking.
OCI Extends Resolution
pkg/devcontainer/config/extends_oci.go (lines 58–93)
resolveOCIExtends accepts context, normalizes OCI ref, and sets Origin using the prefixed form; recursive resolution logic unchanged.
Caching Infrastructure
pkg/devcontainer/config/extends_oci.go (lines 95–207)
Refactored pullOCIExtendsJSON pipeline: parses ref, derives deterministic cache dir via sha256 hash, validates cache via remote.Head digest comparison, fetches image with context-derived keychain, extracts devcontainer.json, and persists cache files (json + digest).
Cache Utilities
pkg/devcontainer/config/extends_oci.go (lines 135–207)
Added helpers: getCacheDir (cache directory derivation), getKeychain (context-aware keychain), isCacheValid (digest comparison), writeCache (persist to disk).
Integration
pkg/devcontainer/config/parse.go
ParseDevContainerJSONFile invokes resolveExtendsArray with context.TODO() to match updated signature.
Tests
pkg/devcontainer/config/extends_oci_test.go
Added/updated tests: TestIsOCIRef expanded for oci:// cases, TestResolveOCIExtends_OCIPrefix validates prefixed ref handling, TestResolveOCIExtends_CacheHit verifies cache files written to XDG_CACHE_HOME and reused, TestResolveOCIExtends_CacheInvalidation confirms cache invalidation on manifest change, all calls updated to pass context.Background().

Sequence Diagram

sequenceDiagram
    participant Caller
    participant ResolveOCI as resolveOCIExtends
    participant Pull as pullOCIExtendsJSON
    participant Cache as Cache System
    participant Registry as OCI Registry
    participant Keychain as Keychain (ctx)
    
    Caller->>ResolveOCI: (ctx, ociRef, visited)
    ResolveOCI->>ResolveOCI: stripOCIPrefix for cycle detection
    ResolveOCI->>Pull: (ctx, stripped ref)
    
    Pull->>Cache: getCacheDir(ref)
    Pull->>Cache: isCacheValid(cached digest, ref)
    alt Cache Valid
        Cache-->>Pull: return cached devcontainer.json
    else Cache Invalid/Missing
        Pull->>Keychain: getKeychain(ctx)
        Pull->>Registry: remote.Head(ref) → current digest
        Pull->>Registry: remote.Image(ref, keychain) → fetch image
        Pull->>Pull: extract devcontainer.json from layer
        Pull->>Cache: writeCache(dir, json, digest)
    end
    
    Pull-->>ResolveOCI: devcontainer.json bytes
    ResolveOCI-->>Caller: *DevContainerConfig
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • devsy-org/devsy#184: Both PRs modify the devcontainer extends resolution pipeline; this PR adds context propagation and OCI caching to a pipeline previously enhanced with local-file extends merging.

Suggested labels

size/xl, area/devcontainer-config, feature/oci-caching

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main enhancements: oci:// prefix support, multi-cloud auth, and digest caching. It matches the primary changes across the modified files.
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.

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


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.

@github-actions github-actions Bot added the size/l label May 6, 2026
@skevetter
skevetter marked this pull request as ready for review May 6, 2026 22:05
@skevetter
skevetter enabled auto-merge (squash) May 6, 2026 22:05
@coderabbitai coderabbitai Bot added the size/xl label May 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@pkg/devcontainer/config/extends_oci.go`:
- Around line 108-117: The cache helpers and OCI calls must accept and use the
request context: add a ctx context.Context parameter to checkExtendsCache and
writeExtendsCache and update their callsites (where cacheDir, ref, kc are
passed) to pass ctx; inside retryOCIExtendsPull's body, add
remote.WithContext(ctx) to remote.Image and remote.Head invocations (in addition
to remote.WithAuthFromKeychain(kc)) so registry fetches honor
cancellation/deadlines; ensure any internal calls within
checkExtendsCache/writeExtendsCache that perform registry I/O also accept and
forward ctx.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57035a14-3d53-41b7-b085-b29d4940690b

📥 Commits

Reviewing files that changed from the base of the PR and between d2ee4ab and 901b5b0.

📒 Files selected for processing (4)
  • pkg/devcontainer/config/extends.go
  • pkg/devcontainer/config/extends_oci.go
  • pkg/devcontainer/config/extends_oci_test.go
  • pkg/devcontainer/config/parse.go

Comment on lines +108 to 117
if data, ok := checkExtendsCache(cacheDir, ref, kc); ok {
return data, nil
}
}

var img v1.Image
err = retryOCIExtendsPull(func() error {
var fetchErr error
img, fetchErr = remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
img, fetchErr = remote.Image(ref, remote.WithAuthFromKeychain(kc))
return fetchErr

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'remote\.(Image|Head)\(' pkg/devcontainer/config/extends_oci.go -C2
rg -n 'WithContext\(' pkg/devcontainer/config/extends_oci.go -C2

Repository: devsy-org/devsy

Length of output: 569


🏁 Script executed:

# Check the pullOCIExtendsJSON function signature to see if ctx is available
rg -n 'func pullOCIExtendsJSON' pkg/devcontainer/config/extends_oci.go -A 3

Repository: devsy-org/devsy

Length of output: 262


🏁 Script executed:

# Check all calls to checkExtendsCache and writeExtendsCache to understand context availability
rg -n '(checkExtendsCache|writeExtendsCache)\(' pkg/devcontainer/config/extends_oci.go -B 2 -A 2

Repository: devsy-org/devsy

Length of output: 757


🏁 Script executed:

# Check the complete function signatures for the helper functions mentioned
rg -n 'func (checkExtendsCache|writeExtendsCache)' pkg/devcontainer/config/extends_oci.go -A 1

Repository: devsy-org/devsy

Length of output: 390


🌐 Web query:

go-containerregistry remote.Image WithContext option

💡 Result:

Use remote.WithContext(ctx) when calling github.com/google/go-containerregistry/pkg/v1/remote.Image to control the context used for all HTTP requests that remote makes. Example: img, err:= remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithContext(ctx),) remote.WithContext(ctx) is a functional option that sets the context used by the remote package’s HTTP client requests, not only the initial manifest fetch. The GoDoc for WithContext notes that the provided context is used for all HTTP requests performed by the given function, and that for remote.Image it will be used for subsequent operations like RawConfigFile and for methods on layers returned by Layers. The default context is context.Background if you don’t set one [1]. So, when you cancel ctx or it times out, in-flight remote HTTP operations (including later layer/config requests) should be affected because requests are issued with req.WithContext(...) using the stored context [2].

Citations:


Add context parameter to cache helpers and propagate context to registry I/O calls.

Lines 116, 173, and 185 call remote.Image and remote.Head without context options, so caller cancellation and deadlines are not honored during OCI fetch and HEAD check operations. Pass ctx through checkExtendsCache and writeExtendsCache, and add remote.WithContext(ctx) to each registry call.

Proposed fix
-		if data, ok := checkExtendsCache(cacheDir, ref, kc); ok {
+		if data, ok := checkExtendsCache(ctx, cacheDir, ref, kc); ok {
 			return data, nil
 		}
-		img, fetchErr = remote.Image(ref, remote.WithAuthFromKeychain(kc))
+		img, fetchErr = remote.Image(ref, remote.WithAuthFromKeychain(kc), remote.WithContext(ctx))
-		writeExtendsCache(cacheDir, ref, kc, data)
+		writeExtendsCache(ctx, cacheDir, ref, kc, data)
-func checkExtendsCache(cacheDir string, ref name.Reference, kc authn.Keychain) ([]byte, bool) {
+func checkExtendsCache(ctx context.Context, cacheDir string, ref name.Reference, kc authn.Keychain) ([]byte, bool) {
-	desc, err := remote.Head(ref, remote.WithAuthFromKeychain(kc))
+	desc, err := remote.Head(ref, remote.WithAuthFromKeychain(kc), remote.WithContext(ctx))
-func writeExtendsCache(cacheDir string, ref name.Reference, kc authn.Keychain, data []byte) {
-	desc, err := remote.Head(ref, remote.WithAuthFromKeychain(kc))
+func writeExtendsCache(ctx context.Context, cacheDir string, ref name.Reference, kc authn.Keychain, data []byte) {
+	desc, err := remote.Head(ref, remote.WithAuthFromKeychain(kc), remote.WithContext(ctx))
🤖 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 `@pkg/devcontainer/config/extends_oci.go` around lines 108 - 117, The cache
helpers and OCI calls must accept and use the request context: add a ctx
context.Context parameter to checkExtendsCache and writeExtendsCache and update
their callsites (where cacheDir, ref, kc are passed) to pass ctx; inside
retryOCIExtendsPull's body, add remote.WithContext(ctx) to remote.Image and
remote.Head invocations (in addition to remote.WithAuthFromKeychain(kc)) so
registry fetches honor cancellation/deadlines; ensure any internal calls within
checkExtendsCache/writeExtendsCache that perform registry I/O also accept and
forward ctx.

Comment on lines +104 to +109
parent, err := resolveExtendsArray(
context.TODO(),
devContainer.Extends,
declaringDir,
visited,
)

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'context\.TODO\(' pkg/devcontainer/config/parse.go -C2
rg -n 'func ParseDevContainerJSONFile' pkg/devcontainer/config/parse.go -C2

Repository: devsy-org/devsy

Length of output: 441


🏁 Script executed:

rg -n 'ParseDevContainerJSONFile' --type go

Repository: devsy-org/devsy

Length of output: 3258


🏁 Script executed:

rg -n 'func resolveExtendsArray' pkg/devcontainer/config/parse.go -A 5

Repository: devsy-org/devsy

Length of output: 41


🏁 Script executed:

head -120 pkg/devcontainer/config/parse.go | tail -50

Repository: devsy-org/devsy

Length of output: 1341


🏁 Script executed:

rg -n 'func resolveExtendsArray' pkg/devcontainer/config/ -A 15

Repository: devsy-org/devsy

Length of output: 1094


🏁 Script executed:

rg -n 'resolveExtendsArray' pkg/devcontainer/config/ -B 2 -A 2

Repository: devsy-org/devsy

Length of output: 1769


🏁 Script executed:

grep -r "context\." pkg/devcontainer/config/parse.go | head -20

Repository: devsy-org/devsy

Length of output: 78


Use caller-provided context in parse entrypoint instead of context.TODO().

Line 105 uses context.TODO() which prevents caller-driven cancellation/deadline during OCI extends resolution. This is problematic for request-level control flow and can block longer than expected during registry I/O.

The suggested patch's breaking change and incomplete backward compatibility wrapper need refinement. Consider instead:

  • Adding a new context-aware function: ParseDevContainerJSONFileWithContext(ctx context.Context, jsonFilePath string)
  • Keeping the existing ParseDevContainerJSONFile signature for backward compatibility, but have it call the new function with a proper context (note: context.Background() alone doesn't solve this—for CLI commands, use request context; for existing callers that can't provide context, use a timeout wrapper)
  • Other code paths in the codebase (e.g., extends_oci.go line 85) already properly thread context through resolveExtendsArray, showing the pattern is established and expected

@skevetter
skevetter merged commit 80f6170 into main May 7, 2026
225 of 230 checks passed
@skevetter
skevetter deleted the 3ae9/oci-extends-enhancements branch May 7, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant