feat(devcontainer): add devcontainer-lock.json lockfile support#724
Conversation
Adds genuine lockfile support to the native Go feature resolver, matching the reference devcontainer CLI: - Generates/updates .devcontainer-lock.json (or devcontainer-lock.json) next to the config during build/up, pinning OCI features by manifest digest and direct-tarball features by sha256, with a version/resolved/integrity entry per feature (dependencies included). - Loads an existing lockfile to pin resolution and verify integrity. - Adds --frozen-lockfile (build and up) which fails instead of writing when the lockfile is missing or does not match, with an early fail-fast check. Includes unit tests for the lockfile logic and e2e coverage under the up-features label (auto-write + frozen reuse, and frozen-missing failure).
✅ Deploy Preview for devsydev canceled.
|
|
Warning Review limit reached
Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds ChangesFeature lockfile support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant BuildPipeline
participant FeatureProcessor
participant Lockfile
participant FeatureSource
CLI->>BuildPipeline: pass frozen-lockfile or no-lockfile
BuildPipeline->>FeatureProcessor: resolve features with lockfile mode
FeatureProcessor->>Lockfile: read pinned entries
FeatureProcessor->>FeatureSource: resolve OCI or tarball feature
FeatureSource-->>FeatureProcessor: resolved reference and integrity
FeatureProcessor->>Lockfile: record and commit entries
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
✅ Deploy Preview for images-devsy-sh canceled.
|
Parse the generated devcontainer-lock.json and assert structural invariants (exactly the declared feature, sha256-formatted integrity, resolved == repo@integrity, semver version) instead of loose substring checks. Add a negative spec that a committed lockfile pinning a non-existent digest is honored and fails resolution.
Add a spec that serves a tarball feature from the in-test http server and asserts the lockfile records the exact integrity (sha256 of the served bytes, computed with the same hash helper devsy uses) and the tarball URL as resolved. Deterministic exact-value check with no upstream coupling.
…frozen-lockfile
Match the official devcontainer CLI, which creates the lockfile by default but
lets you disable it. --no-lockfile turns the feature lockfile off entirely (no
pinning, no writing); it is mutually exclusive with --frozen-lockfile, enforced
via cobra's MarkFlagsMutuallyExclusive on both build and up.
Refactors the two positional lock bools threaded through fetchFeatures into a
lockfileMode{write,frozen,disabled} struct.
Adds unit tests (flag registration, mutual exclusivity, disabled no-op commit)
and an e2e spec asserting --no-lockfile writes no devcontainer-lock.json.
There was a problem hiding this comment.
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 `@cmd/workspace/build.go`:
- Around line 113-117: Update the FrozenLockfile and NoLockfile help text in
cmd/workspace/build.go, the corresponding CLIOptions comments in
pkg/provider/workspace.go, and the up-command help in
cmd/workspace/up/up_flags.go to refer to both supported lockfile names or use
neutral wording such as “the applicable devcontainer lockfile”; keep the
existing option behavior unchanged.
🪄 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 Plus
Run ID: f040eed9-079d-4412-8793-f80f22456310
📒 Files selected for processing (19)
cmd/workspace/build.gocmd/workspace/build_test.gocmd/workspace/up/up.gocmd/workspace/up/up_flags.gocmd/workspace/up/up_test.goe2e/tests/up-features/testdata/docker-features-lockfile-bad/.devcontainer-lock.jsone2e/tests/up-features/testdata/docker-features-lockfile-bad/.devcontainer.jsone2e/tests/up-features/testdata/docker-features-lockfile/.devcontainer.jsone2e/tests/up-features/up_features.gopkg/devcontainer/build.gopkg/devcontainer/compose.gopkg/devcontainer/compose_build.gopkg/devcontainer/feature/extend.gopkg/devcontainer/feature/features.gopkg/devcontainer/feature/features_oci_test.gopkg/devcontainer/feature/lockfile.gopkg/devcontainer/feature/lockfile_test.gopkg/flags/names/names.gopkg/provider/workspace.go
| cliflags.Bool(&cmd.FrozenLockfile, names.FrozenLockfile, false, | ||
| "Fail if devcontainer-lock.json is missing or does not match the resolved features "+ | ||
| "instead of writing it (useful for CI)"), | ||
| cliflags.Bool(&cmd.NoLockfile, names.NoLockfile, false, | ||
| "Disable devcontainer-lock.json generation and verification"), |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document both supported lockfile filenames consistently.
The lockfile is named .devcontainer-lock.json for .devcontainer.json configurations, so naming only devcontainer-lock.json can mislead users preparing frozen builds.
cmd/workspace/build.go#L113-L117: update build help to mention both names or say “the applicable devcontainer lockfile.”pkg/provider/workspace.go#L294-L300: update theCLIOptionscomments with the same neutral/both-name wording.cmd/workspace/up/up_flags.go#L73-L77: update up-command help with the same wording.
📍 Affects 3 files
cmd/workspace/build.go#L113-L117(this comment)pkg/provider/workspace.go#L294-L300cmd/workspace/up/up_flags.go#L73-L77
🤖 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 `@cmd/workspace/build.go` around lines 113 - 117, Update the FrozenLockfile and
NoLockfile help text in cmd/workspace/build.go, the corresponding CLIOptions
comments in pkg/provider/workspace.go, and the up-command help in
cmd/workspace/up/up_flags.go to refer to both supported lockfile names or use
neutral wording such as “the applicable devcontainer lockfile”; keep the
existing option behavior unchanged.
Signed-off-by: GitHub <noreply@github.com>
Signed-off-by: GitHub <noreply@github.com>
Summary
Adds genuine
devcontainer-lock.jsonlockfile support to devsy's native Go feature resolver, matching the reference devcontainer CLI behavior (previously devsy had only a per-feature local SHA-256 cache sidecar, no committed lockfile).{ "features": { "<userFeatureId|tarballUri>": { "version", "resolved", "integrity", "dependsOn"? } } }. Filename is.devcontainer-lock.jsonwhen the config basename starts with a dot, otherwisedevcontainer-lock.json, written in the config's directory.registry/repo@sha256:…) and direct-tarball features bysha256:of the tarball. An existing lockfile is loaded to pin resolution and verify integrity; local (./) features are excluded.ResolveFeatureOrder) load/pin but never write.--frozen-lockfile(onworkspace buildandworkspace up): fails instead of writing — "lockfile does not exist" if missing, "lockfile does not match" if stale. Includes an early fail-fast precondition so CI doesn't download features before rejecting.Implementation
pkg/devcontainer/feature/lockfile.go(types, path resolution, read/write with frozen semantics,lockfileStatefor pin/record/commit).features.go: OCI resolution now returns resolved digest + integrity (with anoci-digestsidecar for cache hits); tarball resolution returns/verifies integrity.extend.go: threads a lockfile through feature resolution and records entries.CLIOptions.FrozenLockfile→ build/up (including compose) →GetExtendedBuildInfo.Tests
up-featureslabel: auto-writes a lockfile then reuses it under--frozen-lockfile, and fails--frozen-lockfilewhen no lockfile exists.Independent code review completed;
go build ./..., unit tests, andgolangci-lint --new-from-rev origin/mainare clean.Summary by CodeRabbit
--frozen-lockfileto require an existing, matchingdevcontainer-lock.json.--no-lockfileto disable lockfile generation and verification.--frozen-lockfileand--no-lockfilemutually exclusive for relevant commands.--frozen-lockfileis used with a missing, mismatched, or invalidly pinned lockfile.