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
31 changes: 23 additions & 8 deletions pkg/bridge/convert.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -166,19 +166,34 @@ 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 {
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 {
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)))
Expand Down
54 changes: 54 additions & 0 deletions pkg/bridge/convert_test.go
Original file line numberDiff line numberDiff line change
@@ -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"})
}
18 changes: 17 additions & 1 deletion pkg/e2e/bridge_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,18 +18,20 @@ package e2e

import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"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) {
Expand DownExpand Up@@ -59,3 +61,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", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion))
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")
}
17 changes: 17 additions & 0 deletions pkg/e2e/fixtures/bridge-build-only/Dockerfile
Original file line numberDiff line numberDiff line change
@@ -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"]
5 changes: 5 additions & 0 deletions pkg/e2e/fixtures/bridge-build-only/compose.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
services:
app:
build: .
expose:
- "8080"
Loading