Skip to content
Open
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
7 changes: 7 additions & 0 deletions cmd/compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,13 @@ func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOpti
}

for _, s := range project.Services {
// mirror the pure-resource provider guard of the image phase of up and
// pull (pkg/compose/provider_images.go): a provider service declaring
// neither image: nor build: has no image reference to print, and the
// default name GetImageNameOrDefault fabricates is never built or pulled
Comment on lines +724 to +727

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "never built or pulled" is not accurate, get-service-config hands that fabricated name to the provider (plugins.go:447). One line is enough, like the guard it mirrors in provider_images.go:

// pure resource provider: no image reference to list

if s.Provider != nil && s.Image == "" && s.Build == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking

This continue also skips the GetDependentImages loop below, so the pre_start hook images of a pure-resource provider service vanish from config --images. pull and up still pull them (pullHookImages and addPreStartHookPulls have no provider guard), and the provider receives the whole resolved service, hooks included, through get-service-config. config --images and pull agreed before this change; they no longer do.

Repro: db with provider: and pre_start: [{image: busybox:1.36.1}], plus web: nginx.

  • before: config --images prints edgecase-db, busybox:1.36.1, nginx
  • after: prints nginx only, while pull --dry-run still pulls busybox:1.36.1

Only the fabricated name has to go:

pure := s.Provider != nil && s.Image == "" && s.Build == nil
if !pure {
	_, _ = fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
}
for _, img := range api.GetDependentImages(s, project.Name) {
	_, _ = fmt.Fprintln(dockerCli.Out(), img)
}

The current test passes with either version, so it needs a hook case (see the test comment).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, follow-up: this is the third spelling of "pure resource provider" (provider_images.go:69-74, and the inverted form in build.go:111 and api.go:311). A small helper next to GetImageNameOrDefault in pkg/api would keep the definition in one place. Fine to do in a separate PR.

continue
}
_, _ = fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
for _, img := range api.GetDependentImages(s, project.Name) {
_, _ = fmt.Fprintln(dockerCli.Out(), img)
Expand Down
45 changes: 45 additions & 0 deletions cmd/compose/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@
package compose

import (
"bytes"
"os"
"path/filepath"
"sort"
"strings"
"testing"

"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -307,3 +312,43 @@ func TestLockModel(t *testing.T) {
},
})
}

// TestRunConfigImagesSkipsPureResourceProvider mirrors the pure-resource
// provider guard of the pull path (pkg/compose/provider_images.go): a
// provider service declaring neither image: nor build: has no image to
// list, and the GetImageNameOrDefault fallback would fabricate a
// "<project>-<service>" reference that is never built and never pulled.
func TestRunConfigImagesSkipsPureResourceProvider(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test:

  • Add a case with a pre_start hook image on the pure-resource provider, expecting the hook image to be listed (pins the change in config.go).
  • Add provider + image: and provider + build: cases, expecting redis and <project>-<service> respectively. They keep the guard narrow.
  • cli.EXPECT().Client() and ConfigFile() are never called; only Out() is needed (the test passes without them, and the MockAPIClient goes away too).
  • sort.Strings on a one-element slice does nothing. assert.Equal(t, buf.String(), "nginx\n") is stricter and shorter, and drops the sort import.
  • ProjectDir: dir is redundant, the compose file's directory is used already.
  • Non-blocking: type: sbx ties a core test to one specific provider. The docs use a neutral name (awesomecloud).

ctrl := gomock.NewController(t)
defer ctrl.Finish()

dir := t.TempDir()
composePath := filepath.Join(dir, "compose.yaml")
assert.NilError(t, os.WriteFile(composePath, []byte(`
name: edgecase
services:
db:
provider:
type: sbx
web:
image: nginx
`), 0o600))

buf := new(bytes.Buffer)
cli := mocks.NewMockCli(ctrl)
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
cli.EXPECT().Client().Return(mocks.NewMockAPIClient(ctrl)).AnyTimes()
cli.EXPECT().ConfigFile().Return(configfile.New("")).AnyTimes()

opts := configOptions{
ProjectOptions: &ProjectOptions{
ConfigPaths: []string{composePath},
ProjectDir: dir,
},
}
assert.NilError(t, runConfigImages(t.Context(), cli, opts, nil))

lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
sort.Strings(lines)
assert.DeepEqual(t, lines, []string{"nginx"})
}
Loading