{{ message }}
engine: HostExecutor interface for initializeCommand#33
Merged
Conversation
… hooks) Adds a caller-supplied HostExecutor interface that the engine dispatches host-side spec hooks to. initializeCommand now routes through it (Single + Parallel forms). secretsCommand will plug into the same interface in a follow-up. Library does NOT ship a default HostExecutor: host execution is security-sensitive (devcontainer.json can declare arbitrary commands), and policy decisions — sandboxing, env filtering, timeouts, working dir — belong to the embedding application. Unconfigured executor + a configured initializeCommand returns a *LifecycleError wrapping a typed sentinel ErrHostExecutorNotConfigured so callers can errors.Is and surface a useful message. Closes #11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
|
@CodeRabbit review |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lifecycle_test.go (1)
302-369: ⚡ Quick winConsider adding test coverage for the Parallel command form.
The tests comprehensively cover the Single command path, but
runInitializeParallel(which handlesinitializeCommandas a map of named commands running concurrently) lacks direct test coverage. Given the concurrency logic involved, a test verifying parallel execution would strengthen confidence.Example test skeleton
func TestInitializeCommand_RoutesToHostExecutorParallel(t *testing.T) { rt := newScriptedRuntime() hx := &fakeHostExecutor{} eng, _ := New(EngineOptions{Runtime: rt, HostExecutor: hx}) ws := writeImageDevcontainer(t, `{ "image":"alpine:3.20", "initializeCommand": { "setup": "echo setup", "prepare": "echo prepare" } }`) if _, err := eng.Up(context.Background(), UpOptions{ LocalWorkspaceFolder: ws, RunInitializeCommand: true, }); err != nil { t.Fatalf("Up: %v", err) } if len(hx.calls) != 2 { t.Fatalf("expected 2 host calls, got %d", len(hx.calls)) } }🤖 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 `@lifecycle_test.go` around lines 302 - 369, Add a unit test that exercises the parallel form of initializeCommand by invoking eng.Up with RunInitializeCommand: true on a devcontainer.json where "initializeCommand" is a map (e.g. {"setup":"echo setup","prepare":"echo prepare"}), using newScriptedRuntime() and a fakeHostExecutor to capture host calls; assert that runInitializeParallel (indirectly via Up/EngineOptions and HostExecutor) results in two HostExecutor calls (fakeHostExecutor.calls length == 2) and that each call’s Shell matches the expected commands, and ensure the test mirrors existing patterns used in TestInitializeCommand_RoutesToHostExecutorSingle and TestInitializeCommand_SkippedByDefault.
🤖 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 `@lifecycle_test.go`:
- Around line 302-369: Add a unit test that exercises the parallel form of
initializeCommand by invoking eng.Up with RunInitializeCommand: true on a
devcontainer.json where "initializeCommand" is a map (e.g. {"setup":"echo
setup","prepare":"echo prepare"}), using newScriptedRuntime() and a
fakeHostExecutor to capture host calls; assert that runInitializeParallel
(indirectly via Up/EngineOptions and HostExecutor) results in two HostExecutor
calls (fakeHostExecutor.calls length == 2) and that each call’s Shell matches
the expected commands, and ensure the test mirrors existing patterns used in
TestInitializeCommand_RoutesToHostExecutorSingle and
TestInitializeCommand_SkippedByDefault.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d54d45e2-4db2-4244-9310-ab89f570351a
📒 Files selected for processing (4)
engine.gohost_executor.golifecycle.golifecycle_test.go
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Closes #11. Unblocks #23 (`secretsCommand`) — same interface; the follow-up just captures stdout and merges into `ContainerEnv`.
API surface
```go
type HostExecutor interface {
ExecHost(ctx context.Context, cmd HostCommand) (HostExecResult, error)
}
type HostCommand struct {
Shell string
Exec []string
Env map[string]string
WorkingDir string
}
type HostExecResult struct {
ExitCode int
Stdout, Stderr string
}
```
Mirrors `runtime.ExecOptions` / `runtime.ExecResult` so consumers building both sides reuse the same mental model. Engine sets `WorkingDir = LocalWorkspaceFolder` for spec hooks.
Why no default impl?
Host execution is the most security-sensitive surface in the spec. `devcontainer.json` is user-supplied; running `exec.Command` from inside a library against it would bake policy (env passthrough, timeouts, sandboxing) into the wrong place. Caller-supplied keeps the library spec-faithful and lets each embedder make the right call:
Test plan
No integration test — host execution is hostile to CI (would need a sandbox), and the contract is fully covered by unit tests via the in-memory `fakeHostExecutor`.
Summary by CodeRabbit
Release Notes
New Features
initializeCommandhooks with output capture and exit code reporting.Tests