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
4 changes: 2 additions & 2 deletions cmd/compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,8 @@ func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[s

func resolveServiceImageDigests(ctx context.Context, dockerCli command.Cli, model map[string]any) error {
// create a pseudo-project so we can rely on WithImagesResolved to resolve images,
// pre_start hook images and `type: image` volume sources, keyed by actual service
// names so sources referencing another service are detected as such and kept unresolved
// pre_start hook images and `type: image` volume sources, keyed by the actual
// service names so the resolved digests can be folded back into the raw model below
p := &types.Project{
Services: types.Services{},
}
Expand Down
11 changes: 9 additions & 2 deletions cmd/compose/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestResolveImageDigests(t *testing.T) {
serviceDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
hookDigest = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
volumeDigest = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
builderDigest = "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
Expand All @@ -60,7 +61,9 @@ func TestResolveImageDigests(t *testing.T) {
map[string]any{"type": "image", "source": "someimage:latest", "target": "/data"},
// already digested: must NOT trigger any registry call and must be kept as-is
map[string]any{"type": "image", "source": "docker.io/library/pinned@" + testDigest, "target": "/pinned"},
// source referencing another service: locally built image, must be kept as-is
// happens to share its name with the "builder" service below: a type=image
// volume source is always a plain docker image reference (compose-go#929),
// never an implicit reference to another service, so it still gets resolved
map[string]any{"type": "image", "source": "builder", "target": "/built"},
map[string]any{"type": "bind", "source": "/host", "target": "/bind"},
"./data:/short",
Expand Down Expand Up @@ -91,6 +94,10 @@ func TestResolveImageDigests(t *testing.T) {
Return(client.DistributionInspectResult{
DistributionInspect: registry.DistributionInspect{Descriptor: ocispec.Descriptor{Digest: hookDigest}},
}, nil)
apiClient.EXPECT().DistributionInspect(gomock.Any(), "docker.io/library/builder:latest", gomock.Any()).
Return(client.DistributionInspectResult{
DistributionInspect: registry.DistributionInspect{Descriptor: ocispec.Descriptor{Digest: builderDigest}},
}, nil)

err := resolveImageDigests(t.Context(), cli, model)
assert.NilError(t, err)
Expand All @@ -105,7 +112,7 @@ func TestResolveImageDigests(t *testing.T) {
volumes := service["volumes"].([]any)
assert.Equal(t, volumes[0].(map[string]any)["source"], "docker.io/library/someimage:latest@"+volumeDigest)
assert.Equal(t, volumes[1].(map[string]any)["source"], "docker.io/library/pinned@"+testDigest)
assert.Equal(t, volumes[2].(map[string]any)["source"], "builder")
assert.Equal(t, volumes[2].(map[string]any)["source"], "docker.io/library/builder:latest@"+builderDigest)
assert.Equal(t, volumes[3].(map[string]any)["source"], "/host")
assert.Equal(t, volumes[4], "./data:/short")
assert.Equal(t, services["builder"].(map[string]any)["image"], "docker.io/library/pinned@"+testDigest)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260910154416-11feead015fc
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260918184426-f18e211cbeaf
github.com/containerd/console v1.0.5
github.com/containerd/containerd/v2 v2.3.5
github.com/containerd/errdefs v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260910154416-11feead015fc h1:rmi1Yz/JylJUNR068ay6XZI9mIOEyMFqWvWS0rWDgt4=
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260910154416-11feead015fc/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ=
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260918184426-f18e211cbeaf h1:ot3ZuEYubwVMNT5sO8/5MONyTkGi+GNxezgUT2Wwf9E=
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260918184426-f18e211cbeaf/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ=
github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=
github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
Expand Down
23 changes: 23 additions & 0 deletions pkg/e2e/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,29 @@ func TestBuildDependentImageWithProfile(t *testing.T) {
OutputContains("secret-build-test Built"))
}

// A service reachable only as another service's additional_contexts build
// dependency must still build even when its own profile is inactive: it's
// referenced for its image, not started as a workload. Regression test for
// docker/compose#14223 — currently fails at project load ("declares unknown
// service base as additional contexts base"): compose-go's consistency check
// rejects an additional_contexts target that's disabled by profile, before
// the build path (which already special-cases DisabledServices, see
// addBuildDependencies) ever runs. Fixed by compose-spec/compose-go#931;
// this test stays red until that dependency bump lands.
func TestBuildAdditionalContextDisabledService(t *testing.T) {
s := NewScenario(t, "a build-only service reachable only via additional_contexts must build with its profile inactive, and never start")
s.Defer(
DockerCmd("image", "rm", "-f", "e2e-additional-context-base:latest").MayFail(),
DockerCmd("image", "rm", "-f", "e2e-additional-context-classroom:latest").MayFail()).
Step("up with only the classroom profile active builds base as a dependency but never starts it",
// --build: a stale image from a previous run must not let this
// step skip the build it exists to exercise.
ComposeCmd("--profile", "classroom", "up", "--build", "-d"),
ServiceState("classroom", "running"),
ImageExists("e2e-additional-context-base:latest"),
ServiceNotCreated("base"))
}

func TestBuildTLS(t *testing.T) {
t.Helper()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
base:
profiles: ["disabled"]
image: e2e-additional-context-base:latest
build:
context: .
dockerfile_inline: |
FROM alpine
RUN echo base > /base-marker

classroom:
profiles: ["classroom"]
image: e2e-additional-context-classroom:latest
build:
context: .
additional_contexts:
base: service:base
dockerfile_inline: |
FROM base
ENTRYPOINT ["sleep", "infinity"]
Loading