From 560fd4cde0be7041ce937fc1b23852fffa81d45b Mon Sep 17 00:00:00 2001 From: Samuel K Date: Wed, 6 May 2026 08:24:32 -0500 Subject: [PATCH] feat(compose): validate hostRequirements in Docker Compose path The Docker Compose container creation path never called ValidateHostRequirements(), unlike the single-container path. Add the advisory validation call before setupContainer so that host requirement warnings propagate through the JSON envelope. --- .../up-docker-compose/host_requirements.go | 69 +++++++++++++++++++ .../.devcontainer.json | 9 +++ .../docker-compose.yaml | 8 +++ pkg/devcontainer/compose.go | 9 ++- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 e2e/tests/up-docker-compose/host_requirements.go create mode 100644 e2e/tests/up-docker-compose/testdata/compose-host-requirements/.devcontainer.json create mode 100644 e2e/tests/up-docker-compose/testdata/compose-host-requirements/docker-compose.yaml diff --git a/e2e/tests/up-docker-compose/host_requirements.go b/e2e/tests/up-docker-compose/host_requirements.go new file mode 100644 index 000000000..33538e64d --- /dev/null +++ b/e2e/tests/up-docker-compose/host_requirements.go @@ -0,0 +1,69 @@ +//go:build linux || darwin || unix + +package up + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + + "github.com/devsy-org/devsy/e2e/framework" + "github.com/devsy-org/devsy/pkg/devcontainer/config" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe( + "testing up docker-compose command host requirements warnings", + ginkgo.Label("up-docker-compose-host-requirements"), + func() { + var btc *baseTestContext + + ginkgo.BeforeEach(func(ctx context.Context) { + var err error + btc = &baseTestContext{} + btc.initialDir, err = os.Getwd() + framework.ExpectNoError(err) + + btc.f, err = setupDockerProvider( + filepath.Join(btc.initialDir, "bin"), "docker", + ) + framework.ExpectNoError(err) + }) + + ginkgo.It("surfaces hostRequirements warnings in JSON envelope", func(ctx context.Context) { + tempDir, err := setupWorkspace( + "tests/up-docker-compose/testdata/compose-host-requirements", + btc.initialDir, + btc.f, + ) + framework.ExpectNoError(err) + + stdout, _, err := btc.f.DevsyUpStreams(ctx, tempDir) + framework.ExpectNoError(err) + + lines := strings.Split(strings.TrimSpace(stdout), "\n") + gomega.Expect(lines).NotTo(gomega.BeEmpty()) + + lastLine := lines[len(lines)-1] + var envelope config.ResultEnvelope + err = json.Unmarshal([]byte(lastLine), &envelope) + framework.ExpectNoError(err) + + gomega.Expect(envelope.Outcome).To(gomega.Equal("success")) + gomega.Expect(envelope.Warnings).NotTo(gomega.BeEmpty()) + + found := false + for _, w := range envelope.Warnings { + if strings.Contains(w, "cpus:") { + found = true + break + } + } + gomega.Expect(found).To(gomega.BeTrue(), + "expected a cpus warning in %v", envelope.Warnings) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + }, +) diff --git a/e2e/tests/up-docker-compose/testdata/compose-host-requirements/.devcontainer.json b/e2e/tests/up-docker-compose/testdata/compose-host-requirements/.devcontainer.json new file mode 100644 index 000000000..fcf770033 --- /dev/null +++ b/e2e/tests/up-docker-compose/testdata/compose-host-requirements/.devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "ComposeHostRequirements", + "dockerComposeFile": "./docker-compose.yaml", + "service": "app", + "workspaceFolder": "/workspaces", + "hostRequirements": { + "cpus": 128 + } +} diff --git a/e2e/tests/up-docker-compose/testdata/compose-host-requirements/docker-compose.yaml b/e2e/tests/up-docker-compose/testdata/compose-host-requirements/docker-compose.yaml new file mode 100644 index 000000000..43dcebf56 --- /dev/null +++ b/e2e/tests/up-docker-compose/testdata/compose-host-requirements/docker-compose.yaml @@ -0,0 +1,8 @@ +version: '3' + +services: + app: + image: ghcr.io/devsy-org/test-images/go:1 + command: sleep infinity + volumes: + - .:/workspaces:cached diff --git a/pkg/devcontainer/compose.go b/pkg/devcontainer/compose.go index b1896bbc9..d69bd3025 100644 --- a/pkg/devcontainer/compose.go +++ b/pkg/devcontainer/compose.go @@ -309,13 +309,20 @@ func (r *runner) runDockerCompose( composeAlias := project.Name mergedConfig.RemoteEnv["COMPOSE_PROJECT_NAME"] = &composeAlias - // setup container + // validate host requirements (advisory only — warnings never block creation) + hostWarnings := config.ValidateHostRequirements( + parsedConfig.Config.HostRequirements, + config.SystemHostInfo{}, + substitutionContext.LocalWorkspaceFolder, + ) + return r.setupContainer(ctx, &setupContainerParams{ rawConfig: parsedConfig.Raw, containerDetails: containerDetails, mergedConfig: mergedConfig, substitutionContext: substitutionContext, timeout: timeout, + hostWarnings: hostWarnings, }) }