Skip to content

fix(config): skip pure resource provider services in config --images - #14332

Open
TDnorthgarden wants to merge 1 commit into
docker:mainfrom
TDnorthgarden:fix/config-images-pure-provider
Open

TDnorthgarden wants to merge 1 commit into
docker:mainfrom
TDnorthgarden:fix/config-images-pure-provider

Conversation

@TDnorthgarden

Copy link
Copy Markdown

Problem

compose config --images lists the image references of a project's services.
For a provider service declaring neither image: nor build: (a "pure
resource" provider, e.g. a managed database), it printed the
<project>-<service> name that GetImageNameOrDefault fabricates — a
reference that is never built (no build section) and never pulled (the image
phase of up and pull explicitly skips such services, see
pkg/compose/provider_images.go).

Reproducer:

name: edgecase
services:
  db:
    provider:
      type: sbx
  web:
    image: nginx
$ docker compose config --images
nginx
edgecase-db        # fabricated: never built, never pulled

Consumers feeding this output to a registry (e.g.
compose config --images | xargs docker pull) try to pull an image that
does not exist.

Fix

Mirror the pure-resource provider guard of the image phase of up and pull
in config --images: skip services with provider: set and neither
image: nor build:.

A provider service declaring neither image: nor build: has no image
reference to list, yet config --images printed the <project>-<service>
name GetImageNameOrDefault fabricates: a reference that is never built
and never pulled, while the image phase of up and pull already skips
such services. Consumers feeding `compose config --images` output to
a registry would try to pull an image that does not exist.

Mirror the pure-resource provider guard of pkg/compose/provider_images.go.

Signed-off-by: Aucoto <tdnorthgarden@gmail.com>
@TDnorthgarden
TDnorthgarden requested review from a team as code owners October 13, 2026 07:09
@TDnorthgarden
TDnorthgarden force-pushed the fix/config-images-pure-provider branch from 1b09409 to 0fb320f Compare October 13, 2026 07:17

@glours glours left a 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.

Review summary: one change needed (config.go:728), the rest is non-blocking.

Comment thread cmd/compose/config.go
// 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
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).

Comment thread cmd/compose/config.go
// 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
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.

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.

Comment thread cmd/compose/config.go
Comment on lines +724 to +727
// 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

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

// 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).

@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants