Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions e2e/tests/readconfiguration/readconfiguration.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,46 @@ var _ = ginkgo.Describe("read-configuration command", ginkgo.Label("read-configu
})
framework.ExpectError(err)
}, ginkgo.SpecTimeout(framework.TimeoutShort()))

ginkgo.DescribeTable("resolves shutdownAction per devcontainer spec",
ginkgo.Label("read-configuration"),
func(ctx context.Context, testdataDir, expected string) {
f := framework.NewDefaultFramework(initialDir + "/bin")
tempDir, err := framework.CopyToTempDirWithoutChdir(testdataDir)
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func() { _ = os.RemoveAll(tempDir) })

stdout, _, err := f.ExecCommandCapture(ctx, []string{
"read-configuration",
"--workspace-folder", tempDir,
"--include-merged-configuration",
})
framework.ExpectNoError(err)

var result map[string]any
err = json.Unmarshal([]byte(stdout), &result)
framework.ExpectNoError(err)

merged, ok := result["mergedConfiguration"].(map[string]any)
gomega.Expect(ok).To(gomega.BeTrue(), "mergedConfiguration should be an object")
gomega.Expect(merged).To(
gomega.HaveKeyWithValue("shutdownAction", expected),
)
},
ginkgo.Entry("defaults to stopContainer for image-based config",
"tests/readconfiguration/testdata-shutdown-action",
"stopContainer",
ginkgo.SpecTimeout(framework.TimeoutShort()),
),
ginkgo.Entry("defaults to stopCompose for docker-compose config",
"tests/readconfiguration/testdata-shutdown-action-compose",
"stopCompose",
ginkgo.SpecTimeout(framework.TimeoutShort()),
),
ginkgo.Entry("preserves explicit none",
"tests/readconfiguration/testdata-shutdown-action-explicit",
"none",
ginkgo.SpecTimeout(framework.TimeoutShort()),
),
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"dockerComposeFile": "docker-compose.yml",
"service": "app"

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

Service name must match the compose service key

Line 3 should match the exact service key in docker-compose.yml. Current CI failure (service 'app1' configured in devcontainer.json not found) indicates this fixture set is still mismatched, so the compose-based e2e case won’t run reliably until aligned.

🤖 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
`@e2e/tests/readconfiguration/testdata-shutdown-action-compose/.devcontainer.json`
at line 3, The devcontainer.json "service" key value is mismatched with the
docker-compose service name; update the "service" entry in .devcontainer.json
from "app" to the exact compose service key "app1" so it matches the service
defined in docker-compose.yml (ensure the "service" property equals "app1" used
by the compose fixture).

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version: "3"
services:
app:
image: ghcr.io/devsy-org/test-images/base:ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"image": "ghcr.io/devsy-org/test-images/base:ubuntu",
"shutdownAction": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"image": "ghcr.io/devsy-org/test-images/base:ubuntu"
}
11 changes: 11 additions & 0 deletions pkg/devcontainer/config/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ func MergeConfiguration(
)
mergedConfig.HostRequirements = mergeHostRequirements(reversed)

if mergedConfig.ShutdownAction == "" {
switch {
case copiedConfig.ShutdownAction != "":
mergedConfig.ShutdownAction = copiedConfig.ShutdownAction
case len(copiedConfig.DockerComposeFile) > 0:
mergedConfig.ShutdownAction = ShutdownActionStopCompose
default:
mergedConfig.ShutdownAction = ShutdownActionStopContainer
}
}

return mergedConfig, nil
}

Expand Down
48 changes: 48 additions & 0 deletions pkg/devcontainer/config/merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,51 @@ func TestMergeLifestyleHooks_MultipleFeatures(t *testing.T) {
t.Errorf("expected image hook last, got %v", got[2])
}
}

func TestMergeConfiguration_ShutdownActionDefault_ImageConfig(t *testing.T) {
cfg := &DevContainerConfig{
ImageContainer: ImageContainer{Image: "ubuntu:latest"},
}
merged, err := MergeConfiguration(cfg, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if merged.ShutdownAction != ShutdownActionStopContainer {
t.Errorf("ShutdownAction = %q, want %q", merged.ShutdownAction, ShutdownActionStopContainer)
}
}

func TestMergeConfiguration_ShutdownActionDefault_ComposeConfig(t *testing.T) {
cfg := &DevContainerConfig{
ComposeContainer: ComposeContainer{
DockerComposeFile: []string{"docker-compose.yml"},
Service: "app",
},
}
merged, err := MergeConfiguration(cfg, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if merged.ShutdownAction != ShutdownActionStopCompose {
t.Errorf("ShutdownAction = %q, want %q", merged.ShutdownAction, ShutdownActionStopCompose)
}
}

func TestMergeConfiguration_ShutdownActionExplicit_NotOverridden(t *testing.T) {
cfg := &DevContainerConfig{
DevContainerConfigBase: DevContainerConfigBase{
ShutdownAction: ShutdownActionNone,
},
ComposeContainer: ComposeContainer{
DockerComposeFile: []string{"docker-compose.yml"},
Service: "app",
},
}
merged, err := MergeConfiguration(cfg, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if merged.ShutdownAction != ShutdownActionNone {
t.Errorf("ShutdownAction = %q, want %q", merged.ShutdownAction, ShutdownActionNone)
}
}
Loading