From cfbde4d35cf6c65754472722f3d51abba49a4084 Mon Sep 17 00:00:00 2001 From: Eric Wyles <23637493+ericwyles@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:48:49 -0500 Subject: [PATCH 1/3] fix(bridge): skip pulling default image references for build-only services Signed-off-by: Eric Wyles <23637493+ericwyles@users.noreply.github.com> --- pkg/bridge/convert.go | 28 +++++++--- pkg/bridge/convert_test.go | 54 +++++++++++++++++++ pkg/e2e/bridge_test.go | 15 ++++++ pkg/e2e/fixtures/bridge-build-only/Dockerfile | 17 ++++++ .../fixtures/bridge-build-only/compose.yaml | 5 ++ 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 pkg/bridge/convert_test.go create mode 100644 pkg/e2e/fixtures/bridge-build-only/Dockerfile create mode 100644 pkg/e2e/fixtures/bridge-build-only/compose.yaml diff --git a/pkg/bridge/convert.go b/pkg/bridge/convert.go index adf676eab4b..fc84e65a50f 100644 --- a/pkg/bridge/convert.go +++ b/pkg/bridge/convert.go @@ -166,19 +166,31 @@ func LoadAdditionalResources(ctx context.Context, dockerCLI command.Cli, project for name, service := range project.Services { imageName := api.GetImageNameOrDefault(service, project.Name) - inspect, err := inspectWithPull(ctx, dockerCLI, imageName) - if err != nil { - return nil, err + var inspect image.InspectResponse + if service.Build != nil && service.Image == "" { + result, err := dockerCLI.Client().ImageInspect(ctx, imageName) + if err != nil && !errdefs.IsNotFound(err) { + return nil, err + } + inspect = result.InspectResponse + } else { + var err error + inspect, err = inspectWithPull(ctx, dockerCLI, imageName) + if err != nil { + return nil, err + } } service.Image = imageName exposed := utils.Set[string]{} exposed.AddAll(service.Expose...) - for port := range inspect.Config.ExposedPorts { - p, err := network.ParsePort(port) - if err != nil { - return nil, err + if inspect.Config != nil { + for port := range inspect.Config.ExposedPorts { + p, err := network.ParsePort(port) + if err != nil { + return nil, err + } + exposed.Add(strconv.Itoa(int(p.Num()))) } - exposed.Add(strconv.Itoa(int(p.Num()))) } for _, port := range service.Ports { exposed.Add(strconv.Itoa(int(port.Target))) diff --git a/pkg/bridge/convert_test.go b/pkg/bridge/convert_test.go new file mode 100644 index 00000000000..6f90bd0415e --- /dev/null +++ b/pkg/bridge/convert_test.go @@ -0,0 +1,54 @@ +/* + Copyright 2026 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package bridge + +import ( + "testing" + + "github.com/compose-spec/compose-go/v2/types" + "github.com/containerd/errdefs" + "github.com/moby/moby/client" + "go.uber.org/mock/gomock" + "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/pkg/mocks" +) + +func TestLoadAdditionalResources_BuildOnlySkipsPull(t *testing.T) { + mockCtrl := gomock.NewController(t) + dockerCLI := mocks.NewMockCli(mockCtrl) + apiClient := mocks.NewMockAPIClient(mockCtrl) + dockerCLI.EXPECT().Client().Return(apiClient).AnyTimes() + apiClient.EXPECT().ImageInspect(gomock.Any(), "test-api"). + Return(client.ImageInspectResult{}, errdefs.ErrNotFound) + + project := &types.Project{ + Name: "test", + Services: types.Services{ + "api": { + Name: "api", + Build: &types.BuildConfig{Context: "."}, + Expose: []string{"8080"}, + }, + }, + } + + actual, err := LoadAdditionalResources(t.Context(), dockerCLI, project) + assert.NilError(t, err) + assert.Equal(t, actual.Services["api"].Image, "test-api") + assert.DeepEqual(t, actual.Services["api"].Expose, types.StringOrNumberList{"8080"}) +} diff --git a/pkg/e2e/bridge_test.go b/pkg/e2e/bridge_test.go index c4c99b8d292..e19182466d1 100644 --- a/pkg/e2e/bridge_test.go +++ b/pkg/e2e/bridge_test.go @@ -18,6 +18,7 @@ package e2e import ( "fmt" + "os" "path/filepath" "strings" "testing" @@ -59,3 +60,17 @@ func TestConvertAndTransformList(t *testing.T) { assert.Assert(t, strings.Contains(res.Stdout(), "docker/compose-bridge-kubernetes"), res.Combined()) }) } + +func TestConvertBuildOnlyService(t *testing.T) { + c := NewParallelCLI(t) + outDir := t.TempDir() + + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge-build-only/compose.yaml", "--project-name", "bridge-build-only", "bridge", "convert", + "--output", outDir, "--transformation", "docker/compose-bridge-kubernetes:v0.0.3") + assert.NilError(t, res.Error) + assert.Equal(t, res.ExitCode, 0) + + entries, err := os.ReadDir(outDir) + assert.NilError(t, err) + assert.Assert(t, len(entries) > 0, "expected bridge conversion to produce output") +} diff --git a/pkg/e2e/fixtures/bridge-build-only/Dockerfile b/pkg/e2e/fixtures/bridge-build-only/Dockerfile new file mode 100644 index 00000000000..be33c1c5a91 --- /dev/null +++ b/pkg/e2e/fixtures/bridge-build-only/Dockerfile @@ -0,0 +1,17 @@ +# Copyright 2026 Docker Compose CLI authors + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM alpine +EXPOSE 8080 +CMD ["echo", "Hello from Dockerfile"] diff --git a/pkg/e2e/fixtures/bridge-build-only/compose.yaml b/pkg/e2e/fixtures/bridge-build-only/compose.yaml new file mode 100644 index 00000000000..4d9019e602c --- /dev/null +++ b/pkg/e2e/fixtures/bridge-build-only/compose.yaml @@ -0,0 +1,5 @@ +services: + app: + build: . + expose: + - "8080" From 31c8cf0408e065c57a366fae7d4842b9eb2faad3 Mon Sep 17 00:00:00 2001 From: Eric Wyles <23637493+ericwyles@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:13:14 -0500 Subject: [PATCH 2/3] Update pkg/bridge/convert.go Co-authored-by: Guillaume Lours <705411+glours@users.noreply.github.com> Signed-off-by: Eric Wyles <23637493+ericwyles@users.noreply.github.com> --- pkg/bridge/convert.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/bridge/convert.go b/pkg/bridge/convert.go index fc84e65a50f..bdb48465aed 100644 --- a/pkg/bridge/convert.go +++ b/pkg/bridge/convert.go @@ -169,8 +169,11 @@ func LoadAdditionalResources(ctx context.Context, dockerCLI command.Cli, project var inspect image.InspectResponse if service.Build != nil && service.Image == "" { result, err := dockerCLI.Client().ImageInspect(ctx, imageName) - if err != nil && !errdefs.IsNotFound(err) { - return nil, err + if err != nil { + if !errdefs.IsNotFound(err) { + return nil, err + } + logrus.Warnf("image %s for service %s not found locally; Dockerfile-exposed ports will not be included — run `docker compose build` first to include them", imageName, name) } inspect = result.InspectResponse } else { From 243c7800925d601fc20f891ecc66c09f6b28741e Mon Sep 17 00:00:00 2001 From: Eric Wyles <23637493+ericwyles@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:19:47 -0500 Subject: [PATCH 3/3] Reuse bridge image version in e2e tests Signed-off-by: Eric Wyles <23637493+ericwyles@users.noreply.github.com> --- pkg/e2e/bridge_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/e2e/bridge_test.go b/pkg/e2e/bridge_test.go index e19182466d1..7212bcaf2c0 100644 --- a/pkg/e2e/bridge_test.go +++ b/pkg/e2e/bridge_test.go @@ -26,11 +26,12 @@ import ( "gotest.tools/v3/assert" ) +const bridgeImageVersion = "v0.0.3" + func TestConvertAndTransformList(t *testing.T) { c := NewParallelCLI(t) const projectName = "bridge" - const bridgeImageVersion = "v0.0.3" tmpDir := t.TempDir() t.Run("kubernetes manifests", func(t *testing.T) { @@ -66,7 +67,7 @@ func TestConvertBuildOnlyService(t *testing.T) { outDir := t.TempDir() res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge-build-only/compose.yaml", "--project-name", "bridge-build-only", "bridge", "convert", - "--output", outDir, "--transformation", "docker/compose-bridge-kubernetes:v0.0.3") + "--output", outDir, "--transformation", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion)) assert.NilError(t, res.Error) assert.Equal(t, res.ExitCode, 0)