mirror of
https://github.com/docker/compose.git
synced 2026-09-27 09:47:22 +00:00
build(deps): adopt compose-go jobs and the container-spec layering
compose-spec/compose-go#866 is merged: jobs top-level element, container specification layered as ContainerSpec/WorkloadSpec, pre_start hooks carrying the full container specification resolved at load time. Bump to the merged head and adapt in the same movement — composite literals setting moved fields wrap them into the embedded ContainerSpec / WorkloadSpec (promoted field access was already source-compatible, so this is literal-only, no behavior change), and pre_start handling is typed against PreStartHook. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
95929928c1
commit
e96ca020f2
34 changed files with 507 additions and 495 deletions
|
|
@ -31,10 +31,9 @@ func TestFilterServices(t *testing.T) {
|
|||
Links: []string{"bar"},
|
||||
},
|
||||
"bar": {
|
||||
Name: "bar",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
Name: "bar", WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
|
||||
"zot": {},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"zot": {
|
||||
Name: "zot",
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ func imagesOnly(project *types.Project) *types.Project {
|
|||
digests := types.Services{}
|
||||
for name, config := range project.Services {
|
||||
service := types.ServiceConfig{
|
||||
Image: config.Image,
|
||||
ContainerSpec: types.ContainerSpec{Image: config.Image},
|
||||
}
|
||||
for _, vol := range config.Volumes {
|
||||
if vol.Type == types.VolumeTypeImage {
|
||||
|
|
@ -386,7 +386,7 @@ func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[s
|
|||
}
|
||||
for _, hook := range preStartHooks(service) {
|
||||
image, _ := hook["image"].(string)
|
||||
config.PreStart = append(config.PreStart, types.ServiceHook{Image: image})
|
||||
config.PreStart = append(config.PreStart, types.PreStartHook{ContainerSpec: types.ContainerSpec{Image: image}})
|
||||
}
|
||||
for _, volume := range imageVolumes(service) {
|
||||
source, _ := volume["source"].(string)
|
||||
|
|
|
|||
|
|
@ -135,14 +135,17 @@ func TestImagesOnly(t *testing.T) {
|
|||
Name: "test",
|
||||
Services: types.Services{
|
||||
"test": types.ServiceConfig{
|
||||
Name: "test",
|
||||
Image: "docker.io/library/nginx@" + testDigest,
|
||||
Command: types.ShellCommand{"echo", "hello"},
|
||||
Name: "test",
|
||||
|
||||
// hooks can't be overridden element-wise on merge, so the lock must not carry them
|
||||
PreStart: []types.ServiceHook{{Image: "docker.io/library/hookimage@" + testDigest}},
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "docker.io/library/someimage@" + testDigest, Target: "/data"},
|
||||
{Type: types.VolumeTypeBind, Source: "/host", Target: "/bind"},
|
||||
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Image: "docker.io/library/hookimage@" + testDigest}}}, ContainerSpec: types.ContainerSpec{
|
||||
Image: "docker.io/library/nginx@" + testDigest,
|
||||
Command: types.ShellCommand{"echo", "hello"},
|
||||
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "docker.io/library/someimage@" + testDigest, Target: "/data"},
|
||||
{Type: types.VolumeTypeBind, Source: "/host", Target: "/bind"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -154,9 +157,11 @@ func TestImagesOnly(t *testing.T) {
|
|||
assert.DeepEqual(t, locked, &types.Project{
|
||||
Services: types.Services{
|
||||
"test": types.ServiceConfig{
|
||||
Image: "docker.io/library/nginx@" + testDigest,
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "docker.io/library/someimage@" + testDigest, Target: "/data"},
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "docker.io/library/nginx@" + testDigest,
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "docker.io/library/someimage@" + testDigest, Target: "/data"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -167,8 +172,8 @@ func TestWarnHooksNotLockable(t *testing.T) {
|
|||
messages := captureWarnings(t, func() {
|
||||
warnHooksNotLockable(&types.Project{
|
||||
Services: types.Services{
|
||||
"with-hook-image": types.ServiceConfig{PreStart: []types.ServiceHook{{Image: "alpine:latest"}}},
|
||||
"inline-hook": types.ServiceConfig{PreStart: []types.ServiceHook{{Command: types.ShellCommand{"echo"}}}},
|
||||
"with-hook-image": types.ServiceConfig{PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Image: "alpine:latest"}}}},
|
||||
"inline-hook": types.ServiceConfig{PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"echo"}}}}},
|
||||
"without-hook": types.ServiceConfig{},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -38,17 +38,18 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
|||
return &types.Project{
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{
|
||||
Image: "foo",
|
||||
|
||||
Platform: "alice/32",
|
||||
}, WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"alice/32",
|
||||
},
|
||||
},
|
||||
Platform: "alice/32",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -75,15 +76,13 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
|
|||
},
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{Image: "foo"}, WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -110,15 +109,13 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
|
|||
},
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{Image: "foo"}, WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,22 +27,19 @@ func TestApplyPullOptions(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"must-build": {
|
||||
Name: "must-build",
|
||||
Name: "must-build", WorkloadSpec:
|
||||
// No image, local build only
|
||||
Build: &types.BuildConfig{
|
||||
types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
}},
|
||||
},
|
||||
"has-build": {
|
||||
Name: "has-build",
|
||||
Image: "registry.example.com/myservice",
|
||||
Build: &types.BuildConfig{
|
||||
Name: "has-build", ContainerSpec: types.ContainerSpec{Image: "registry.example.com/myservice"}, WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
}},
|
||||
},
|
||||
"must-pull": {
|
||||
Name: "must-pull",
|
||||
Image: "registry.example.com/another-service",
|
||||
Name: "must-pull", ContainerSpec: types.ContainerSpec{Image: "registry.example.com/another-service"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,8 +155,7 @@ services:
|
|||
WorkingDir: dir,
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{Image: "nginx"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -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.20260908103050-cda18529aca7
|
||||
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260910154416-11feead015fc
|
||||
github.com/containerd/console v1.0.5
|
||||
github.com/containerd/containerd/v2 v2.3.5
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -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.20260908103050-cda18529aca7 h1:D9jScaeUAk7DjyB71SD9zvOFPR6klw0FxPcR2bgu72c=
|
||||
github.com/compose-spec/compose-go/v2 v2.15.1-0.20260908103050-cda18529aca7/go.mod h1:Q1+qtN4vhzEjGrnqRtzx1xa8raDZQlMUe3WJxndYNiQ=
|
||||
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/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=
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ package locker
|
|||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v5/internal/pidfile"
|
||||
"github.com/mitchellh/go-ps"
|
||||
|
||||
"github.com/docker/compose/v5/internal/pidfile"
|
||||
)
|
||||
|
||||
func (f *Pidfile) Lock() error {
|
||||
|
|
|
|||
|
|
@ -28,27 +28,27 @@ func TestProjectHash(t *testing.T) {
|
|||
Name: "fake-proj",
|
||||
WorkingDir: "/tmp",
|
||||
Services: map[string]types.ServiceConfig{
|
||||
"foo": {Image: "fake-image"},
|
||||
"foo": {ContainerSpec: types.ContainerSpec{Image: "fake-image"}},
|
||||
},
|
||||
DisabledServices: map[string]types.ServiceConfig{
|
||||
"bar": {Image: "diff-image"},
|
||||
"bar": {ContainerSpec: types.ContainerSpec{Image: "diff-image"}},
|
||||
},
|
||||
}
|
||||
projB := &types.Project{
|
||||
Name: "fake-proj",
|
||||
WorkingDir: "/tmp",
|
||||
Services: map[string]types.ServiceConfig{
|
||||
"foo": {Image: "fake-image"},
|
||||
"bar": {Image: "diff-image"},
|
||||
"foo": {ContainerSpec: types.ContainerSpec{Image: "fake-image"}},
|
||||
"bar": {ContainerSpec: types.ContainerSpec{Image: "diff-image"}},
|
||||
},
|
||||
}
|
||||
projC := &types.Project{
|
||||
Name: "fake-proj",
|
||||
WorkingDir: "/tmp",
|
||||
Services: map[string]types.ServiceConfig{
|
||||
"foo": {Image: "fake-image"},
|
||||
"bar": {Image: "diff-image"},
|
||||
"baz": {Image: "yet-another-image"},
|
||||
"foo": {ContainerSpec: types.ContainerSpec{Image: "fake-image"}},
|
||||
"bar": {ContainerSpec: types.ContainerSpec{Image: "diff-image"}},
|
||||
"baz": {ContainerSpec: types.ContainerSpec{Image: "yet-another-image"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,58 +62,57 @@ func TestGetDependentImages(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "no hooks",
|
||||
service: types.ServiceConfig{Image: "alpine:3.20"},
|
||||
service: types.ServiceConfig{ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"}},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "pre_start hook with explicit image",
|
||||
service: types.ServiceConfig{
|
||||
Image: "alpine:3.20",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "init"}},
|
||||
},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "init"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"},
|
||||
},
|
||||
expected: []string{"alpine:3.19"},
|
||||
},
|
||||
{
|
||||
name: "pre_start hook without image is ignored",
|
||||
service: types.ServiceConfig{
|
||||
Image: "alpine:3.20",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "busybox", Command: types.ShellCommand{"echo", "a"}},
|
||||
{Command: types.ShellCommand{"echo", "b"}},
|
||||
},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "busybox", Command: types.ShellCommand{"echo", "a"}}},
|
||||
{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"echo", "b"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"},
|
||||
},
|
||||
expected: []string{"busybox"},
|
||||
},
|
||||
{
|
||||
name: "pre_start hook reusing the service image is ignored",
|
||||
service: types.ServiceConfig{
|
||||
Image: "alpine:3.20",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine:3.20", Command: types.ShellCommand{"echo", "same"}},
|
||||
{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "other"}},
|
||||
},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine:3.20", Command: types.ShellCommand{"echo", "same"}}},
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "other"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"},
|
||||
},
|
||||
expected: []string{"alpine:3.19"},
|
||||
},
|
||||
{
|
||||
name: "pre_start hook reusing the default (build) image name is ignored",
|
||||
service: types.ServiceConfig{
|
||||
Name: "web",
|
||||
Build: &types.BuildConfig{Context: "."},
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "demo-web", Command: types.ShellCommand{"echo", "same"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "demo-web", Command: types.ShellCommand{"echo", "same"}}},
|
||||
}, WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{Context: "."}},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
// exec hooks carry no image at all since the container spec
|
||||
// layering: nothing to collect, by construction
|
||||
name: "post_start and pre_stop hooks are not collected",
|
||||
service: types.ServiceConfig{
|
||||
Image: "alpine:3.20",
|
||||
PostStart: []types.ServiceHook{{Image: "ignored:post", Command: types.ShellCommand{"echo"}}},
|
||||
PreStop: []types.ServiceHook{{Image: "ignored:stop", Command: types.ShellCommand{"echo"}}},
|
||||
PostStart: []types.ServiceHook{{Command: types.ShellCommand{"echo"}}},
|
||||
PreStop: []types.ServiceHook{{Command: types.ShellCommand{"echo"}}},
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ func TestLoadAdditionalResources_BuildOnlySkipsPull(t *testing.T) {
|
|||
Name: "test",
|
||||
Services: types.Services{
|
||||
"api": {
|
||||
Name: "api",
|
||||
Build: &types.BuildConfig{Context: "."},
|
||||
Expose: []string{"8080"},
|
||||
Name: "api", WorkloadSpec: types.WorkloadSpec{
|
||||
Build: &types.BuildConfig{Context: "."},
|
||||
Expose: []string{"8080"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,26 +84,22 @@ func Test_dockerFilePath(t *testing.T) {
|
|||
func Test_addBuildDependencies(t *testing.T) {
|
||||
project := &types.Project{Services: types.Services{
|
||||
"test": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"foo": "service:foo",
|
||||
"bar": "service:bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"foo": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"zot": "service:zot",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
},
|
||||
"zot": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
}},
|
||||
},
|
||||
"bar": types.ServiceConfig{WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{}}},
|
||||
"zot": types.ServiceConfig{WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{}}},
|
||||
}}
|
||||
|
||||
services := addBuildDependencies([]string{"test"}, project)
|
||||
|
|
@ -122,11 +118,11 @@ func TestGetLocalImagesDigests_PreStartHook(t *testing.T) {
|
|||
Name: "demo",
|
||||
Services: types.Services{
|
||||
"web": types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine:3.20",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "init"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine:3.19", Command: types.ShellCommand{"echo", "init"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine:3.20"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -155,9 +151,10 @@ func TestResolveImageVolumes(t *testing.T) {
|
|||
|
||||
t.Run("source is an image name", func(t *testing.T) {
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("content:1", "/data")},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("content:1", "/data")},
|
||||
},
|
||||
}
|
||||
resolveImageVolumes(&service, images, "p")
|
||||
assert.Equal(t, service.Volumes[0].Source, "content:1")
|
||||
|
|
@ -166,9 +163,10 @@ func TestResolveImageVolumes(t *testing.T) {
|
|||
|
||||
t.Run("source is another service resolves to its image name", func(t *testing.T) {
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("source", "/data")},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("source", "/data")},
|
||||
},
|
||||
}
|
||||
resolveImageVolumes(&service, images, "p")
|
||||
// the mount Source must stay a daemon-resolvable name, never a digest
|
||||
|
|
@ -178,9 +176,10 @@ func TestResolveImageVolumes(t *testing.T) {
|
|||
|
||||
t.Run("unresolvable source is left untouched and unlabelled", func(t *testing.T) {
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("ghost:1", "/data")},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{imageVolume("ghost:1", "/data")},
|
||||
},
|
||||
}
|
||||
resolveImageVolumes(&service, images, "p")
|
||||
assert.Equal(t, service.Volumes[0].Source, "ghost:1")
|
||||
|
|
@ -190,11 +189,12 @@ func TestResolveImageVolumes(t *testing.T) {
|
|||
|
||||
t.Run("several volumes produce a deterministic sorted label", func(t *testing.T) {
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
imageVolume("assets:2", "/b"),
|
||||
imageVolume("content:1", "/a"),
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
imageVolume("assets:2", "/b"),
|
||||
imageVolume("content:1", "/a"),
|
||||
},
|
||||
},
|
||||
}
|
||||
resolveImageVolumes(&service, images, "p")
|
||||
|
|
@ -203,9 +203,10 @@ func TestResolveImageVolumes(t *testing.T) {
|
|||
|
||||
t.Run("no image volumes writes no label", func(t *testing.T) {
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{{Type: types.VolumeTypeVolume, Source: "vol", Target: "/data"}},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
CustomLabels: types.Labels{},
|
||||
Volumes: []types.ServiceVolumeConfig{{Type: types.VolumeTypeVolume, Source: "vol", Target: "/data"}},
|
||||
},
|
||||
}
|
||||
resolveImageVolumes(&service, images, "p")
|
||||
_, labelled := service.CustomLabels[api.ImageVolumeDigestLabel]
|
||||
|
|
|
|||
|
|
@ -376,9 +376,11 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
|||
service, ok := set[serviceLabel]
|
||||
if !ok {
|
||||
service = types.ServiceConfig{
|
||||
Name: serviceLabel,
|
||||
Image: ctr.Image,
|
||||
Labels: ctr.Labels,
|
||||
Name: serviceLabel,
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: ctr.Image,
|
||||
Labels: ctr.Labels,
|
||||
},
|
||||
}
|
||||
}
|
||||
service.Scale = increment(service.Scale)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ func TestBuildVolumeMount(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServiceImageName(t *testing.T) {
|
||||
assert.Equal(t, api.GetImageNameOrDefault(composetypes.ServiceConfig{Image: "myImage"}, "myProject"), "myImage")
|
||||
assert.Equal(t, api.GetImageNameOrDefault(composetypes.ServiceConfig{ContainerSpec: composetypes.ContainerSpec{Image: "myImage"}}, "myProject"), "myImage")
|
||||
assert.Equal(t, api.GetImageNameOrDefault(composetypes.ServiceConfig{Name: "aService"}, "myProject"), "myProject-aService")
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +108,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
|||
Name: "myProject",
|
||||
Services: composetypes.Services{
|
||||
"myService": {
|
||||
Name: "myService",
|
||||
Volumes: []composetypes.ServiceVolumeConfig{
|
||||
Name: "myService", ContainerSpec: composetypes.ContainerSpec{Volumes: []composetypes.ServiceVolumeConfig{
|
||||
{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Target: "/var/myvolume1",
|
||||
|
|
@ -131,7 +130,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
|||
Source: "\\\\.\\pipe\\docker_engine_windows",
|
||||
Target: "\\\\.\\pipe\\docker_engine",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
Volumes: composetypes.Volumes(map[string]composetypes.VolumeConfig{
|
||||
|
|
@ -194,15 +193,14 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
|||
func TestDefaultNetworkSettings(t *testing.T) {
|
||||
t.Run("returns the network with the highest priority as primary when service has multiple networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
Name: "myService", ContainerSpec: composetypes.ContainerSpec{Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"myNetwork1": {
|
||||
Priority: 10,
|
||||
},
|
||||
"myNetwork2": {
|
||||
Priority: 1000,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
|
|
@ -275,11 +273,10 @@ func TestDefaultNetworkSettings(t *testing.T) {
|
|||
|
||||
t.Run("returns only primary network in EndpointsConfig for API < 1.44", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
Name: "myService", ContainerSpec: composetypes.ContainerSpec{Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"myNetwork1": {Priority: 10},
|
||||
"myNetwork2": {Priority: 1000},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
|
|
@ -299,8 +296,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
|
|||
|
||||
t.Run("returns defined network mode if explicitly set", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
NetworkMode: "host",
|
||||
Name: "myService", ContainerSpec: composetypes.ContainerSpec{NetworkMode: "host"},
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
|
|
@ -324,8 +320,7 @@ func TestCreateEndpointSettings(t *testing.T) {
|
|||
Name: "projName",
|
||||
}, composetypes.ServiceConfig{
|
||||
Name: "serviceName",
|
||||
ContainerName: "containerName",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
ContainerName: "containerName", ContainerSpec: composetypes.ContainerSpec{Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"netName": {
|
||||
Priority: 100,
|
||||
Aliases: []string{"alias1", "alias2"},
|
||||
|
|
@ -338,7 +333,7 @@ func TestCreateEndpointSettings(t *testing.T) {
|
|||
"driverOpt2": "optval2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, 0, "netName", []string{"link1", "link2"}, true)
|
||||
assert.NilError(t, err)
|
||||
macAddr, _ := net.ParseMAC("02:00:00:00:00:01")
|
||||
|
|
|
|||
|
|
@ -34,16 +34,14 @@ func createTestProject() *types.Project {
|
|||
return &types.Project{
|
||||
Services: types.Services{
|
||||
"test1": {
|
||||
Name: "test1",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
Name: "test1", WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
|
||||
"test2": {},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"test2": {
|
||||
Name: "test2",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
Name: "test2", WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
|
||||
"test3": {},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"test3": {
|
||||
Name: "test3",
|
||||
|
|
@ -54,8 +52,7 @@ func createTestProject() *types.Project {
|
|||
|
||||
func TestTraversalWithMultipleParents(t *testing.T) {
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent",
|
||||
DependsOn: make(types.DependsOnConfig),
|
||||
Name: "dependent", WorkloadSpec: types.WorkloadSpec{DependsOn: make(types.DependsOnConfig)},
|
||||
}
|
||||
|
||||
project := types.Project{
|
||||
|
|
@ -122,8 +119,7 @@ func TestBuildGraph(t *testing.T) {
|
|||
desc: "builds graph with single service",
|
||||
services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
|
|
@ -140,12 +136,10 @@ func TestBuildGraph(t *testing.T) {
|
|||
desc: "builds graph with two separate services",
|
||||
services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
|
||||
},
|
||||
"another": {
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
Name: "another", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
|
|
@ -169,14 +163,12 @@ func TestBuildGraph(t *testing.T) {
|
|||
desc: "builds graph with a service and a dependency",
|
||||
services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"another": {
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
Name: "another", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
|
|
@ -204,20 +196,17 @@ func TestBuildGraph(t *testing.T) {
|
|||
desc: "builds graph with multiple dependency levels",
|
||||
services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"another": {
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "another", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"another_dep": types.ServiceDependency{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"another_dep": {
|
||||
Name: "another_dep",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
Name: "another_dep", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{}},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
|
|
@ -281,15 +270,14 @@ func TestBuildGraphDependsOn(t *testing.T) {
|
|||
desc: "service depends on init container which is already removed",
|
||||
services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"test-removed-init-container": types.ServiceDependency{
|
||||
Condition: "service_completed_successfully",
|
||||
Restart: false,
|
||||
Extensions: types.Extensions(nil),
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
|
|
@ -331,10 +319,10 @@ func TestNewGraphDoesNotMutateProject(t *testing.T) {
|
|||
Services: types.Services{
|
||||
"app": {
|
||||
Name: "app",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted, Required: true},
|
||||
"debug": {Condition: types.ServiceConditionStarted, Required: false},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"db": {Name: "db"},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -295,11 +295,11 @@ func TestDownRemoveImages(t *testing.T) {
|
|||
Name: strings.ToLower(testProject),
|
||||
Services: types.Services{
|
||||
"local-anonymous": {Name: "local-anonymous"},
|
||||
"local-named": {Name: "local-named", Image: "local-named-image"},
|
||||
"remote": {Name: "remote", Image: "remote-image"},
|
||||
"remote-tagged": {Name: "remote-tagged", Image: "registry.example.com/remote-image-tagged:v1.0"},
|
||||
"local-named": {Name: "local-named", ContainerSpec: types.ContainerSpec{Image: "local-named-image"}},
|
||||
"remote": {Name: "remote", ContainerSpec: types.ContainerSpec{Image: "remote-image"}},
|
||||
"remote-tagged": {Name: "remote-tagged", ContainerSpec: types.ContainerSpec{Image: "registry.example.com/remote-image-tagged:v1.0"}},
|
||||
"no-images-anonymous": {Name: "no-images-anonymous"},
|
||||
"no-images-named": {Name: "no-images-named", Image: "missing-named-image"},
|
||||
"no-images-named": {Name: "no-images-named", ContainerSpec: types.ContainerSpec{Image: "missing-named-image"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -531,7 +531,7 @@ func TestEnsureImagesDown_SparesDanglingImagesOfOrphanedServices(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Name: "prj",
|
||||
Services: types.Services{
|
||||
"web": {Name: "web", Image: "web-image"},
|
||||
"web": {Name: "web", ContainerSpec: types.ContainerSpec{Image: "web-image"}},
|
||||
},
|
||||
}
|
||||
apiClient.EXPECT().ImageList(gomock.Any(), client.ImageListOptions{
|
||||
|
|
@ -570,7 +570,7 @@ func TestEnsureImagesDown_RemoveOrphansAlsoTakesDanglingImages(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Name: "prj",
|
||||
Services: types.Services{
|
||||
"web": {Name: "web", Image: "web-image"},
|
||||
"web": {Name: "web", ContainerSpec: types.ContainerSpec{Image: "web-image"}},
|
||||
},
|
||||
}
|
||||
apiClient.EXPECT().ImageList(gomock.Any(), client.ImageListOptions{
|
||||
|
|
|
|||
|
|
@ -85,9 +85,11 @@ func (s *composeService) createProjectFromContainers(containers []container.Summ
|
|||
service, ok := services[serviceLabel]
|
||||
if !ok {
|
||||
service = types.ServiceConfig{
|
||||
Name: serviceLabel,
|
||||
Image: ctr.Image,
|
||||
Labels: ctr.Labels,
|
||||
Name: serviceLabel,
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: ctr.Image,
|
||||
Labels: ctr.Labels,
|
||||
},
|
||||
}
|
||||
}
|
||||
service.Scale = increment(service.Scale)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ func serviceConfig(replicas int) types.ServiceConfig {
|
|||
Deploy: &types.DeployConfig{
|
||||
Replicas: &replicas,
|
||||
},
|
||||
Name: "foo",
|
||||
Image: "bar",
|
||||
Name: "foo", ContainerSpec: types.ContainerSpec{Image: "bar"},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,8 +87,7 @@ func TestRunHook_ConsoleSize(t *testing.T) {
|
|||
mockCli.EXPECT().Out().Return(streams.NewOut(tty)).AnyTimes()
|
||||
|
||||
service := types.ServiceConfig{
|
||||
Name: "test",
|
||||
Tty: tc.tty,
|
||||
Name: "test", WorkloadSpec: types.WorkloadSpec{Tty: tc.tty},
|
||||
}
|
||||
hook := types.ServiceHook{Command: []string{"echo", "hello"}}
|
||||
ctr := container.Summary{ID: "container123"}
|
||||
|
|
@ -450,7 +449,7 @@ func TestRunHook_CopyError(t *testing.T) {
|
|||
mockCli.EXPECT().Out().Return(streams.NewOut(os.Stdout)).AnyTimes()
|
||||
|
||||
ctr := container.Summary{ID: "ctr-1"}
|
||||
service := types.ServiceConfig{Name: "svc", Tty: false}
|
||||
service := types.ServiceConfig{Name: "svc", WorkloadSpec: types.WorkloadSpec{Tty: false}}
|
||||
hook := types.ServiceHook{Command: types.ShellCommand{"true"}}
|
||||
|
||||
mockAPI.EXPECT().
|
||||
|
|
@ -572,7 +571,7 @@ func TestRunHook_ExecInspectError(t *testing.T) {
|
|||
mockCli.EXPECT().Out().Return(streams.NewOut(os.Stdout)).AnyTimes()
|
||||
|
||||
ctr := container.Summary{ID: "ctr-1"}
|
||||
service := types.ServiceConfig{Name: "svc", Tty: false}
|
||||
service := types.ServiceConfig{Name: "svc", WorkloadSpec: types.WorkloadSpec{Tty: false}}
|
||||
hook := types.ServiceHook{Command: types.ShellCommand{"true"}}
|
||||
|
||||
mockAPI.EXPECT().
|
||||
|
|
|
|||
|
|
@ -389,10 +389,11 @@ func TestPlatformPinnedDigest(t *testing.T) {
|
|||
Name: "p",
|
||||
Services: types.Services{
|
||||
"app": {
|
||||
Name: "app",
|
||||
Image: "foo:1",
|
||||
Platform: "linux/s390x",
|
||||
CustomLabels: types.Labels{},
|
||||
Name: "app", ContainerSpec: types.ContainerSpec{
|
||||
Image: "foo:1",
|
||||
Platform: "linux/s390x",
|
||||
CustomLabels: types.Labels{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -455,7 +456,7 @@ func TestPlatformPinnedDigest(t *testing.T) {
|
|||
// whichever service triggered it, so services sharing the image with another
|
||||
// pinned platform must re-resolve theirs.
|
||||
func TestServiceImageDigest(t *testing.T) {
|
||||
pinnedService := types.ServiceConfig{Name: "app", Platform: "linux/s390x"}
|
||||
pinnedService := types.ServiceConfig{Name: "app", ContainerSpec: types.ContainerSpec{Platform: "linux/s390x"}}
|
||||
|
||||
t.Run("unpinned service uses the shared digest, no inspect", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ func (s *composeService) runPreStart(ctx context.Context, project *types.Project
|
|||
|
||||
func (s *composeService) runPreStartHook(
|
||||
ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
ctr container.Summary, index int, hook types.ServiceHook, listener api.ContainerEventListener,
|
||||
ctr container.Summary, index int, hook types.PreStartHook, listener api.ContainerEventListener,
|
||||
) error {
|
||||
created, err := s.createPreStartContainer(ctx, project, service, ctr, hook)
|
||||
if err != nil {
|
||||
|
|
@ -164,7 +164,7 @@ func (s *composeService) runPreStartHook(
|
|||
|
||||
func (s *composeService) createPreStartContainer(
|
||||
ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
ctr container.Summary, hook types.ServiceHook,
|
||||
ctr container.Summary, hook types.PreStartHook,
|
||||
) (client.ContainerCreateResult, error) {
|
||||
image := hook.Image
|
||||
if image == "" {
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@ func TestPreStart_SuccessTwoHooksInOrder(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"echo", "first"}},
|
||||
{Image: "alpine", Command: types.ShellCommand{"echo", "second"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"echo", "first"}}},
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"echo", "second"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -134,12 +134,12 @@ func TestPreStart_FirstHookFailsStopsExecution(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"false"}},
|
||||
{Image: "alpine", Command: types.ShellCommand{"echo", "never"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"false"}}},
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"echo", "never"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -164,11 +164,11 @@ func TestPreStart_PerReplicaRejected(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}, PerReplica: true},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{PerReplica: true, ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -184,8 +184,8 @@ func TestPreStart_ImageFallsBackToBuiltImage(t *testing.T) {
|
|||
// Service with no explicit image (build-only); hook image also empty.
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Command: types.ShellCommand{"echo", "hi"}},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"echo", "hi"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
|
@ -215,11 +215,11 @@ func TestPreStart_ExplicitHookImageUsed(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "service-image:latest",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "custom-hook-image:1.2.3", Command: types.ShellCommand{"echo"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "custom-hook-image:1.2.3", Command: types.ShellCommand{"echo"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "service-image:latest"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -248,11 +248,11 @@ func TestPreStart_VolumesFromServiceContainer(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -290,12 +290,12 @@ func TestPreStart_ContainerCreateFailurePropagates(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "missing:latest", Command: types.ShellCommand{"true"}},
|
||||
{Image: "alpine", Command: types.ShellCommand{"never"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "missing:latest", Command: types.ShellCommand{"true"}}},
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"never"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -312,11 +312,11 @@ func TestPreStart_ContainerStartFailurePropagates(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -350,11 +350,11 @@ func TestPreStart_WaitResultPreferredOverNilError(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
},
|
||||
Name: "web",
|
||||
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
}, ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
||||
|
|
@ -434,10 +434,10 @@ func TestPreStart_DetachedModeAttachesLogs(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
|
@ -466,10 +466,10 @@ func TestPreStart_FailureIncludesTail(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "demo"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "db",
|
||||
Image: "postgres",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "postgres", Command: types.ShellCommand{"migrate"}},
|
||||
Name: "db",
|
||||
ContainerSpec: types.ContainerSpec{Image: "postgres"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "postgres", Command: types.ShellCommand{"migrate"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "service-ctr-id"}
|
||||
|
|
@ -510,10 +510,10 @@ func TestPreStart_SuccessRemovesContainer(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -549,10 +549,10 @@ func TestPreStart_FailureRetainsContainer(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"migrate"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"migrate"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -588,10 +588,10 @@ func TestPreStart_CancellationRemovesContainer(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"long-running-op"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"long-running-op"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -634,10 +634,10 @@ func TestPreStart_RemovesOrphanBeforeRun(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"migrate"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"migrate"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -671,10 +671,10 @@ func TestPreStart_SuccessRemoveFailureIsNonFatal(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -763,10 +763,10 @@ func TestPreStart_StreamLogsError_NilListener(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -795,10 +795,10 @@ func TestPreStart_StreamLogsError_WithListener(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -838,13 +838,15 @@ func TestPreStart_OldAPIVersion(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
},
|
||||
},
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -878,11 +880,13 @@ func TestPreStart_ConnectExtraNetworksSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -907,11 +911,13 @@ func TestPreStart_ConnectExtraNetworksFails(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -931,10 +937,10 @@ func TestPreStart_ContainerStartFailureAndRemoveFails(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -965,10 +971,10 @@ func TestPreStart_OrphanScanFails(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -998,10 +1004,10 @@ func TestPreStart_OrphanRemovalFails(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -1042,14 +1048,16 @@ func TestPreStart_OldAPINetworkConnectFails(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
},
|
||||
},
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -1082,14 +1090,16 @@ func TestPreStart_OldAPINetworkConnectAndRemoveFails(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"default": nil,
|
||||
"extra": nil,
|
||||
},
|
||||
},
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -1125,10 +1135,10 @@ func TestPreStart_RuntimeAPIVersionError(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "alpine", Command: types.ShellCommand{"true"}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "alpine", Command: types.ShellCommand{"true"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
@ -1153,10 +1163,10 @@ func TestPreStart_FailureStdoutOnlyTail(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "proj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "db",
|
||||
Image: "postgres",
|
||||
PreStart: []types.ServiceHook{
|
||||
{Image: "postgres", Command: types.ShellCommand{"migrate"}},
|
||||
Name: "db",
|
||||
ContainerSpec: types.ContainerSpec{Image: "postgres"},
|
||||
PreStart: []types.PreStartHook{
|
||||
{ContainerSpec: types.ContainerSpec{Image: "postgres", Command: types.ShellCommand{"migrate"}}},
|
||||
},
|
||||
}
|
||||
ctr := container.Summary{ID: "svc-ctr"}
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
|
|||
}
|
||||
for name, service := range project.Services {
|
||||
override.Services[name] = types.ServiceConfig{
|
||||
Image: service.Image,
|
||||
ContainerSpec: types.ContainerSpec{Image: service.Image},
|
||||
}
|
||||
}
|
||||
return override.MarshalYAML()
|
||||
|
|
|
|||
|
|
@ -121,10 +121,11 @@ func Test_preChecks_sensitive_data_detected_decline(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: envPath, Required: true},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: envPath, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -214,10 +215,11 @@ func Test_checkForSensitiveData_optional_env_file_missing(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: filepath.Join(dir, "missing.env"), Required: false},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: filepath.Join(dir, "missing.env"), Required: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -237,10 +239,11 @@ func Test_checkForSensitiveData_optional_env_file_present(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: envPath, Required: false},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: envPath, Required: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -257,10 +260,11 @@ func Test_checkForSensitiveData_required_env_file_missing(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: filepath.Join(dir, "missing.env"), Required: true},
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx",
|
||||
EnvFiles: []types.EnvFile{
|
||||
{Path: filepath.Join(dir, "missing.env"), Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -703,13 +707,14 @@ func Test_publish_decline_returns_ErrCanceled(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Image: "nginx",
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: "/host/path",
|
||||
Target: "/container/path",
|
||||
Name: "web", ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx",
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: "/host/path",
|
||||
Target: "/container/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -747,11 +752,14 @@ func Test_generateImageDigestsOverride_resolvesDependentImages(t *testing.T) {
|
|||
Name: "test",
|
||||
Services: types.Services{
|
||||
"app": types.ServiceConfig{
|
||||
Name: "app",
|
||||
Image: "nginx:latest",
|
||||
PreStart: []types.ServiceHook{{Image: "hookimage:latest"}},
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "someimage:latest", Target: "/data"},
|
||||
Name: "app",
|
||||
|
||||
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Image: "hookimage:latest"}}}, ContainerSpec: types.ContainerSpec{
|
||||
Image: "nginx:latest",
|
||||
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
{Type: types.VolumeTypeImage, Source: "someimage:latest", Target: "/data"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func (p *imagePuller) pullHookImages(ctx context.Context) error {
|
|||
hookPolicy = types.PullPolicyMissing
|
||||
}
|
||||
for _, img := range api.GetDependentImages(service, p.project.Name) {
|
||||
pullRequired, skipReason, err := shouldPullImage(types.ServiceConfig{Name: name, Image: img, PullPolicy: hookPolicy}, p.images)
|
||||
pullRequired, skipReason, err := shouldPullImage(types.ServiceConfig{Name: name, ContainerSpec: types.ContainerSpec{Image: img, PullPolicy: hookPolicy}}, p.images)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ func (p *imagePuller) pullHookImages(ctx context.Context) error {
|
|||
continue
|
||||
}
|
||||
p.scheduled[img] = name
|
||||
hookService := types.ServiceConfig{Name: name, Image: img}
|
||||
hookService := types.ServiceConfig{Name: name, ContainerSpec: types.ContainerSpec{Image: img}}
|
||||
p.eg.Go(func() error {
|
||||
err := p.pullServiceImage(ctx, hookService, p.opts.Quiet, p.project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
if err != nil && !p.opts.IgnoreFailures {
|
||||
|
|
@ -410,8 +410,8 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
|||
// Hack: create a fake ServiceConfig so we pull missing volume image
|
||||
n := fmt.Sprintf("%s:volume %d", name, i)
|
||||
needPull[n] = types.ServiceConfig{
|
||||
Name: n,
|
||||
Image: vol.Source,
|
||||
Name: n,
|
||||
ContainerSpec: types.ContainerSpec{Image: vol.Source},
|
||||
}
|
||||
scheduled[vol.Source] = true
|
||||
}
|
||||
|
|
@ -506,7 +506,7 @@ func addPreStartHookPulls(project *types.Project, images map[string]api.ImageSum
|
|||
hookPolicy = types.PullPolicyMissing
|
||||
}
|
||||
for i, img := range api.GetDependentImages(service, project.Name) {
|
||||
pull, err := mustPull(types.ServiceConfig{Name: name, Image: img, PullPolicy: hookPolicy}, images)
|
||||
pull, err := mustPull(types.ServiceConfig{Name: name, ContainerSpec: types.ContainerSpec{Image: img, PullPolicy: hookPolicy}}, images)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -517,8 +517,8 @@ func addPreStartHookPulls(project *types.Project, images map[string]api.ImageSum
|
|||
// Hack: create a fake ServiceConfig so we pull missing pre_start hook image
|
||||
n := fmt.Sprintf("%s:pre_start %d", name, i)
|
||||
needPull[n] = types.ServiceConfig{
|
||||
Name: n,
|
||||
Image: img,
|
||||
Name: n,
|
||||
ContainerSpec: types.ContainerSpec{Image: img},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,12 @@ func scheduledHookImages(t *testing.T, project *types.Project, present map[strin
|
|||
|
||||
func serviceWithHook(name, img, policy string) types.ServiceConfig {
|
||||
return types.ServiceConfig{
|
||||
Name: name,
|
||||
Image: img,
|
||||
PullPolicy: policy,
|
||||
PreStart: []types.ServiceHook{{Image: "init:latest"}},
|
||||
Name: name,
|
||||
|
||||
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Image: "init:latest"}}}, ContainerSpec: types.ContainerSpec{
|
||||
Image: img,
|
||||
PullPolicy: policy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,8 +168,8 @@ func TestPullRequiredImagesUsesContentDigest(t *testing.T) {
|
|||
project := &types.Project{
|
||||
Name: "demo",
|
||||
Services: types.Services{
|
||||
"web": {Name: "web", Image: ref},
|
||||
"pinned": {Name: "pinned", Image: ref, Platform: "linux/amd64"},
|
||||
"web": {Name: "web", ContainerSpec: types.ContainerSpec{Image: ref}},
|
||||
"pinned": {Name: "pinned", ContainerSpec: types.ContainerSpec{Image: ref, Platform: "linux/amd64"}},
|
||||
},
|
||||
}
|
||||
images := map[string]api.ImageSummary{}
|
||||
|
|
@ -197,7 +199,7 @@ func TestShouldPullImage(t *testing.T) {
|
|||
"old:1": {LastTagTime: time.Now().Add(-48 * time.Hour)},
|
||||
}
|
||||
svc := func(image, policy string) types.ServiceConfig {
|
||||
return types.ServiceConfig{Name: "web", Image: image, PullPolicy: policy}
|
||||
return types.ServiceConfig{Name: "web", ContainerSpec: types.ContainerSpec{Image: image, PullPolicy: policy}}
|
||||
}
|
||||
|
||||
t.Run("no explicit policy always refreshes", func(t *testing.T) {
|
||||
|
|
@ -286,9 +288,9 @@ func TestShouldPullImageProvider(t *testing.T) {
|
|||
images := map[string]api.ImageSummary{}
|
||||
|
||||
pull, _, err := shouldPullImage(types.ServiceConfig{
|
||||
Name: "db",
|
||||
Image: "db:1",
|
||||
Provider: &types.ServiceProviderConfig{Type: "acme"},
|
||||
Name: "db",
|
||||
|
||||
Provider: &types.ServiceProviderConfig{Type: "acme"}, ContainerSpec: types.ContainerSpec{Image: "db:1"},
|
||||
}, images)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, pull, "provider service with a declared image must be pulled")
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ func networkAttachedContainer(t *testing.T, svc types.ServiceConfig, id string)
|
|||
// network is removed then recreated, and the same containers are reconnected —
|
||||
// not recreated.
|
||||
func TestReconcileNetworks_Diverged(t *testing.T) {
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
Networks: types.Networks{"frontend": {Name: "myproject_frontend", Driver: "overlay"}},
|
||||
|
|
@ -189,7 +189,7 @@ func TestReconcileNetworks_Diverged(t *testing.T) {
|
|||
// recreated (by reconcileContainers), with the reconnect ordered before the old
|
||||
// container's removal so they don't race.
|
||||
func TestReconcileNetworks_DivergedAlsoRecreatesChangedContainer(t *testing.T) {
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
Networks: types.Networks{"frontend": {Name: "myproject_frontend", Driver: "overlay"}},
|
||||
|
|
@ -232,14 +232,12 @@ func TestReconcileNetworks_DivergedMultipleServices(t *testing.T) {
|
|||
},
|
||||
Services: types.Services{
|
||||
"web": {
|
||||
Name: "web",
|
||||
Scale: intPtr(1),
|
||||
Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}},
|
||||
Name: "web",
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}},
|
||||
},
|
||||
"api": {
|
||||
Name: "api",
|
||||
Scale: intPtr(1),
|
||||
Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}},
|
||||
Name: "api",
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -281,7 +279,7 @@ func TestReconcileNetworks_DivergedMultipleServices(t *testing.T) {
|
|||
// migration), so a network still in use by non-Compose containers cannot block a
|
||||
// rename.
|
||||
func TestReconcileNetworks_Renamed(t *testing.T) {
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}
|
||||
web := types.ServiceConfig{Name: "web", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{"frontend": {}}}}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
Networks: types.Networks{"frontend": {Name: "myproject_frontend_v2", Driver: "overlay"}},
|
||||
|
|
@ -431,9 +429,8 @@ func divergedVolumeProject(t *testing.T, count, scale int) (*types.Project, *Obs
|
|||
for s := 0; s < count; s++ {
|
||||
name := fmt.Sprintf("db%d", s)
|
||||
svc := types.ServiceConfig{
|
||||
Name: name,
|
||||
Scale: intPtr(scale),
|
||||
Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}},
|
||||
Name: name,
|
||||
Scale: intPtr(scale), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}}},
|
||||
}
|
||||
project.Services[name] = svc
|
||||
hash := mustServiceHash(t, svc)
|
||||
|
|
@ -569,11 +566,10 @@ func TestReconcileVolumes_DivergedConfirmedSharedContainer(t *testing.T) {
|
|||
vol2 := types.VolumeConfig{Name: "myproject_data2", Driver: "local"}
|
||||
svc := types.ServiceConfig{
|
||||
Name: "db",
|
||||
Scale: intPtr(1),
|
||||
Volumes: []types.ServiceVolumeConfig{
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{
|
||||
{Source: "data1", Type: "volume"},
|
||||
{Source: "data2", Type: "volume"},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
@ -620,8 +616,8 @@ func TestReconcileVolumes_DivergedConfirmedSharedContainer(t *testing.T) {
|
|||
func TestReconcileVolumes_DivergedPartialConfirm(t *testing.T) {
|
||||
vol1 := types.VolumeConfig{Name: "myproject_data1", Driver: "local"}
|
||||
vol2 := types.VolumeConfig{Name: "myproject_data2", Driver: "local"}
|
||||
svc1 := types.ServiceConfig{Name: "db1", Scale: intPtr(1), Volumes: []types.ServiceVolumeConfig{{Source: "data1", Type: "volume"}}}
|
||||
svc2 := types.ServiceConfig{Name: "db2", Scale: intPtr(1), Volumes: []types.ServiceVolumeConfig{{Source: "data2", Type: "volume"}}}
|
||||
svc1 := types.ServiceConfig{Name: "db1", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{{Source: "data1", Type: "volume"}}}}
|
||||
svc2 := types.ServiceConfig{Name: "db2", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{{Source: "data2", Type: "volume"}}}}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
Volumes: types.Volumes{"data1": vol1, "data2": vol2},
|
||||
|
|
@ -680,17 +676,22 @@ func TestReconcileVolumes_DivergedPartialConfirm(t *testing.T) {
|
|||
func TestReconcileVolumes_DivergedCascadesToDependent(t *testing.T) {
|
||||
vol := types.VolumeConfig{Name: "myproject_data", Driver: "local"}
|
||||
owner := types.ServiceConfig{
|
||||
Name: "owner",
|
||||
Image: "alpine",
|
||||
Scale: intPtr(1),
|
||||
Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}},
|
||||
Name: "owner",
|
||||
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
|
||||
Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}},
|
||||
},
|
||||
}
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent",
|
||||
Image: "alpine",
|
||||
Scale: intPtr(1),
|
||||
VolumesFrom: []string{"owner"},
|
||||
DependsOn: types.DependsOnConfig{"owner": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}},
|
||||
Name: "dependent",
|
||||
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
|
||||
VolumesFrom: []string{"owner"},
|
||||
}, WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{"owner": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
@ -748,19 +749,24 @@ func TestReconcileVolumes_DivergedCascadesToDependent(t *testing.T) {
|
|||
func TestReconcileVolumes_DivergedVolumesFromRemovedBeforeVolume(t *testing.T) {
|
||||
vol := types.VolumeConfig{Name: "myproject_data", Driver: "local"}
|
||||
owner := types.ServiceConfig{
|
||||
Name: "owner",
|
||||
Image: "alpine",
|
||||
Scale: intPtr(1),
|
||||
Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}},
|
||||
Name: "owner",
|
||||
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
|
||||
Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}},
|
||||
},
|
||||
}
|
||||
// consumer inherits owner's mounts (including data) but never declares the
|
||||
// volume itself.
|
||||
consumer := types.ServiceConfig{
|
||||
Name: "consumer",
|
||||
Image: "alpine",
|
||||
Scale: intPtr(1),
|
||||
VolumesFrom: []string{"owner"},
|
||||
DependsOn: types.DependsOnConfig{"owner": {Condition: types.ServiceConditionStarted, Required: true}},
|
||||
Name: "consumer",
|
||||
|
||||
Scale: intPtr(1), ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
|
||||
VolumesFrom: []string{"owner"},
|
||||
}, WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{"owner": {Condition: types.ServiceConditionStarted, Required: true}}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
@ -831,7 +837,7 @@ func TestReconcileVolumes_UnmanagedMatchReused(t *testing.T) {
|
|||
Name: "myproject",
|
||||
Volumes: types.Volumes{"data": {Name: "myproject_data", Driver: "local"}},
|
||||
Services: types.Services{
|
||||
"db": {Name: "db", Scale: intPtr(1), Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}}},
|
||||
"db": {Name: "db", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}}}},
|
||||
},
|
||||
}
|
||||
dbHash := mustServiceHash(t, project.Services["db"])
|
||||
|
|
@ -894,7 +900,7 @@ func TestReconcileVolumes_RenamedMigratesContainers(t *testing.T) {
|
|||
Name: "myproject",
|
||||
Volumes: types.Volumes{"data": {Name: "myproject_data_v2", Driver: "local"}},
|
||||
Services: types.Services{
|
||||
"db": {Name: "db", Scale: intPtr(1), Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}}},
|
||||
"db": {Name: "db", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Volumes: []types.ServiceVolumeConfig{{Source: "data", Type: "volume"}}}},
|
||||
},
|
||||
}
|
||||
dbHash := mustServiceHash(t, project.Services["db"])
|
||||
|
|
@ -1258,10 +1264,9 @@ func TestReconcileContainers_DependsOnChain(t *testing.T) {
|
|||
"db": {Name: "db", Scale: intPtr(1)},
|
||||
"web": {
|
||||
Name: "web",
|
||||
Scale: intPtr(1),
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Scale: intPtr(1), WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1296,10 +1301,9 @@ func TestReconcileContainers_DependsOnScaleDown(t *testing.T) {
|
|||
"db": svc,
|
||||
"web": {
|
||||
Name: "web",
|
||||
Scale: intPtr(1),
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Scale: intPtr(1), WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1428,8 +1432,8 @@ func TestReconcileContainers_ServiceReference_NoRecreate(t *testing.T) {
|
|||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
parent := types.ServiceConfig{Name: "parent", Image: "alpine", Scale: intPtr(1)}
|
||||
dependent := types.ServiceConfig{Name: "dependent", Image: "alpine", Scale: intPtr(1)}
|
||||
parent := types.ServiceConfig{Name: "parent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
dependent := types.ServiceConfig{Name: "dependent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
tc.mutate(&dependent)
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
@ -1450,10 +1454,11 @@ func TestReconcileContainers_ServiceReference_NoRecreate(t *testing.T) {
|
|||
// produces for namespace-sharing services, so planStopDependents fires too —
|
||||
// the test also asserts the resulting Stop is not duplicated.
|
||||
func TestReconcileContainers_NamespaceParentRecreated_CascadesToDependent(t *testing.T) {
|
||||
parent := types.ServiceConfig{Name: "parent", Image: "alpine", Scale: intPtr(1)}
|
||||
parent := types.ServiceConfig{Name: "parent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent", Image: "alpine", Scale: intPtr(1), NetworkMode: "service:parent",
|
||||
DependsOn: types.DependsOnConfig{"parent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}},
|
||||
Name: "dependent", Scale: intPtr(1),
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine", NetworkMode: "service:parent"},
|
||||
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{"parent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
@ -1479,17 +1484,20 @@ func TestReconcileContainers_NamespaceParentRecreated_CascadesToDependent(t *tes
|
|||
// non-matching parent: a dependent sharing namespace with two parents must
|
||||
// cascade-recreate when either parent is scheduled for recreation.
|
||||
func TestReconcileContainers_MultipleParents_EitherTriggersCascade(t *testing.T) {
|
||||
netParent := types.ServiceConfig{Name: "netparent", Image: "alpine", Scale: intPtr(1)}
|
||||
volParent := types.ServiceConfig{Name: "volparent", Image: "alpine", Scale: intPtr(1)}
|
||||
netParent := types.ServiceConfig{Name: "netparent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
volParent := types.ServiceConfig{Name: "volparent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent", Image: "alpine", Scale: intPtr(1),
|
||||
NetworkMode: "service:netparent",
|
||||
VolumesFrom: []string{"volparent"},
|
||||
// Mirrors what compose-go's normalizer injects for namespace-sharing
|
||||
// references, so the dependency graph orders parents before dependent.
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"netparent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true},
|
||||
"volparent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true},
|
||||
Name: "dependent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{
|
||||
Image: "alpine",
|
||||
NetworkMode: "service:netparent",
|
||||
VolumesFrom: []string{"volparent"},
|
||||
}, WorkloadSpec: types.WorkloadSpec{
|
||||
// Mirrors what compose-go's normalizer injects for namespace-sharing
|
||||
// references, so the dependency graph orders parents before dependent.
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"netparent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true},
|
||||
"volparent": {Condition: types.ServiceConditionStarted, Restart: true, Required: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
project := &types.Project{
|
||||
|
|
@ -1548,10 +1556,11 @@ func TestReconcileContainers_MultipleParents_EitherTriggersCascade(t *testing.T)
|
|||
// TestReconcileContainers_RegularDependsOn_NoCascade ensures the cascade fires
|
||||
// only for namespace/volume-sharing dependencies, not for plain depends_on.
|
||||
func TestReconcileContainers_RegularDependsOn_NoCascade(t *testing.T) {
|
||||
parent := types.ServiceConfig{Name: "parent", Image: "alpine", Scale: intPtr(1)}
|
||||
parent := types.ServiceConfig{Name: "parent", Scale: intPtr(1), ContainerSpec: types.ContainerSpec{Image: "alpine"}}
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent", Image: "alpine", Scale: intPtr(1),
|
||||
DependsOn: types.DependsOnConfig{"parent": {Condition: types.ServiceConditionStarted, Restart: true}},
|
||||
Name: "dependent", Scale: intPtr(1),
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{"parent": {Condition: types.ServiceConditionStarted, Restart: true}}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "myproject",
|
||||
|
|
|
|||
|
|
@ -51,16 +51,14 @@ func TestPrepareRestartProject(t *testing.T) {
|
|||
Name: "prj",
|
||||
Services: types.Services{
|
||||
"proxy": {
|
||||
Name: "proxy",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "proxy", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"web": {Condition: types.ServiceConditionStarted, Restart: true, Required: true},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"web": {
|
||||
Name: "web",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "web", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted, Restart: false, Required: true},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"db": {Name: "db"},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -249,13 +249,12 @@ func TestWaitDependencies(t *testing.T) {
|
|||
replicas := 0
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
|
||||
"app": {
|
||||
Name: "app",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "app", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"disabled": {
|
||||
Condition: ServiceConditionRunningOrHealthy,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"disabled": {
|
||||
Name: "disabled",
|
||||
|
|
@ -487,15 +486,14 @@ func TestCreateMobyContainer(t *testing.T) {
|
|||
apiClient.EXPECT().ClientVersion().Return("1.44").AnyTimes()
|
||||
|
||||
service := types.ServiceConfig{
|
||||
Name: "test",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"a": {
|
||||
Priority: 10,
|
||||
},
|
||||
"b": {
|
||||
Priority: 100,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := types.Project{
|
||||
Name: "bork",
|
||||
|
|
@ -586,11 +584,10 @@ func TestCreateMobyContainerLegacyAPI(t *testing.T) {
|
|||
apiClient.EXPECT().ClientVersion().Return("1.43").AnyTimes()
|
||||
|
||||
service := types.ServiceConfig{
|
||||
Name: "test",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"a": {Priority: 10},
|
||||
"b": {Priority: 100},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := types.Project{
|
||||
Name: "bork",
|
||||
|
|
@ -675,11 +672,10 @@ func TestCreateMobyContainerLegacyAPI_NetworkConnectFailure(t *testing.T) {
|
|||
apiClient.EXPECT().ClientVersion().Return("1.43").AnyTimes()
|
||||
|
||||
service := types.ServiceConfig{
|
||||
Name: "test",
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
Name: "test", ContainerSpec: types.ContainerSpec{Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"a": {Priority: 10},
|
||||
"b": {Priority: 100},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := types.Project{
|
||||
Name: "bork",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
|
@ -89,6 +90,10 @@ func newStartTestService(t *testing.T) (*composeService, *mocks.MockAPIClient, *
|
|||
apiClient.EXPECT().Ping(gomock.Any(), client.PingOptions{NegotiateAPIVersion: true}).
|
||||
Return(client.PingResult{APIVersion: "1.44"}, nil).AnyTimes()
|
||||
apiClient.EXPECT().ClientVersion().Return("1.44").AnyTimes()
|
||||
// the generic pre_start inheritance goes through getCreateConfigs, which
|
||||
// reads the CLI configuration and the daemon host
|
||||
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
|
||||
apiClient.EXPECT().DaemonHost().Return("unix:///var/run/docker.sock").AnyTimes()
|
||||
|
||||
rec := &recordingEventProcessor{}
|
||||
svc, err := NewComposeService(cli, WithEventProcessor(rec))
|
||||
|
|
@ -164,7 +169,7 @@ func TestStartService_StartsOnlyStoppedReplicas(t *testing.T) {
|
|||
project := &types.Project{Name: "prj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
PreStart: []types.ServiceHook{{Command: types.ShellCommand{"init"}}},
|
||||
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"init"}}}},
|
||||
}
|
||||
running := serviceContainer("web", 1, container.StateRunning)
|
||||
stopped := serviceContainer("web", 2, container.StateExited)
|
||||
|
|
@ -194,13 +199,14 @@ func TestStartService_PreStartOnLowestReplica(t *testing.T) {
|
|||
|
||||
project := &types.Project{Name: "prj"}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Image: "alpine",
|
||||
PreStart: []types.ServiceHook{{Command: types.ShellCommand{"init"}}},
|
||||
Name: "web",
|
||||
ContainerSpec: types.ContainerSpec{Image: "alpine"},
|
||||
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"init"}}}},
|
||||
}
|
||||
// Listed out of order on purpose: replica 2 first.
|
||||
|
||||
replica2 := serviceContainer("web", 2, container.StateExited)
|
||||
replica1 := serviceContainer("web", 1, container.StateExited)
|
||||
// Listed out of order on purpose: replica 2 first.
|
||||
containers := Containers{replica2, replica1}
|
||||
|
||||
// runPreStart sweeps orphan hook containers from any previous failed run
|
||||
|
|
@ -276,9 +282,9 @@ func TestStartServiceContainer_Order(t *testing.T) {
|
|||
},
|
||||
}
|
||||
service := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Secrets: []types.ServiceSecretConfig{{Source: "token"}},
|
||||
PostStart: []types.ServiceHook{{Command: types.ShellCommand{"notify"}}},
|
||||
Name: "web",
|
||||
|
||||
PostStart: []types.ServiceHook{{Command: types.ShellCommand{"notify"}}}, ContainerSpec: types.ContainerSpec{Secrets: []types.ServiceSecretConfig{{Source: "token"}}},
|
||||
}
|
||||
ctr := serviceContainer("web", 1, container.StateExited)
|
||||
|
||||
|
|
@ -350,10 +356,9 @@ func TestStartServiceContainer_FailedPostStart(t *testing.T) {
|
|||
func TestGetDependencyCondition(t *testing.T) {
|
||||
oneShot := types.ServiceConfig{Name: "migrate"}
|
||||
web := types.ServiceConfig{
|
||||
Name: "web",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
Name: "web", WorkloadSpec: types.WorkloadSpec{DependsOn: types.DependsOnConfig{
|
||||
"migrate": {Condition: types.ServiceConditionCompletedSuccessfully},
|
||||
},
|
||||
}},
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: "prj",
|
||||
|
|
|
|||
|
|
@ -35,9 +35,13 @@ func TestViz(t *testing.T) {
|
|||
WorkingDir: "/home",
|
||||
Services: types.Services{
|
||||
"service1": {
|
||||
Name: "service1",
|
||||
Image: "image-for-service1",
|
||||
Ports: []types.ServicePortConfig{
|
||||
Name: "service1", ContainerSpec: types.ContainerSpec{
|
||||
Image: "image-for-service1",
|
||||
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"internal": nil,
|
||||
},
|
||||
}, WorkloadSpec: types.WorkloadSpec{Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Published: "80",
|
||||
Target: 80,
|
||||
|
|
@ -48,51 +52,47 @@ func TestViz(t *testing.T) {
|
|||
Target: 533,
|
||||
Protocol: "udp",
|
||||
},
|
||||
},
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"internal": nil,
|
||||
},
|
||||
}},
|
||||
},
|
||||
"service2": {
|
||||
Name: "service2",
|
||||
Image: "image-for-service2",
|
||||
Ports: []types.ServicePortConfig{},
|
||||
Name: "service2", ContainerSpec: types.ContainerSpec{Image: "image-for-service2"}, WorkloadSpec: types.WorkloadSpec{Ports: []types.ServicePortConfig{}},
|
||||
},
|
||||
"service3": {
|
||||
Name: "service3",
|
||||
Image: "some-image",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
Name: "service3", ContainerSpec: types.ContainerSpec{Image: "some-image"}, WorkloadSpec: types.WorkloadSpec{DependsOn: map[string]types.ServiceDependency{
|
||||
"service2": {},
|
||||
"service1": {},
|
||||
},
|
||||
}},
|
||||
},
|
||||
"service4": {
|
||||
Name: "service4",
|
||||
Image: "another-image",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"service3": {},
|
||||
},
|
||||
Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Published: "8080",
|
||||
Target: 80,
|
||||
Name: "service4", ContainerSpec: types.ContainerSpec{
|
||||
Image: "another-image",
|
||||
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"external": nil,
|
||||
},
|
||||
}, WorkloadSpec: types.WorkloadSpec{
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"service3": {},
|
||||
},
|
||||
Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Published: "8080",
|
||||
Target: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
Networks: map[string]*types.ServiceNetworkConfig{
|
||||
"external": nil,
|
||||
},
|
||||
},
|
||||
"With host IP": {
|
||||
Name: "With host IP",
|
||||
Image: "user/image-name",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"service1": {},
|
||||
},
|
||||
Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Published: "8888",
|
||||
Target: 8080,
|
||||
HostIP: "127.0.0.1",
|
||||
Name: "With host IP", ContainerSpec: types.ContainerSpec{Image: "user/image-name"}, WorkloadSpec: types.WorkloadSpec{
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"service1": {},
|
||||
},
|
||||
Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Published: "8888",
|
||||
Target: 8080,
|
||||
HostIP: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -280,8 +280,7 @@ func TestInitialSyncFilesRegularFile(t *testing.T) {
|
|||
|
||||
syncer := &fakeSyncer{synced: make(chan []*sync.PathMapping, 1)}
|
||||
err := (&composeService{}).initialSync(t.Context(), types.ServiceConfig{
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{Context: hostDir},
|
||||
Name: "svc", WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{Context: hostDir}},
|
||||
}, types.Trigger{
|
||||
Path: hostFile,
|
||||
Target: "/app/test.txt",
|
||||
|
|
@ -305,8 +304,8 @@ func TestInitialSync_ExcludesDockerfileAndComposeFiles(t *testing.T) {
|
|||
|
||||
syncer := &fakeSyncer{synced: make(chan []*sync.PathMapping, 1)}
|
||||
err := (&composeService{}).initialSync(t.Context(), types.ServiceConfig{
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{Context: hostDir},
|
||||
Name: "svc",
|
||||
WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{Context: hostDir}},
|
||||
}, types.Trigger{
|
||||
Path: hostDir,
|
||||
Target: "/app",
|
||||
|
|
@ -331,8 +330,8 @@ func TestInitialSync_ExcludesCustomNamedDockerfile(t *testing.T) {
|
|||
|
||||
syncer := &fakeSyncer{synced: make(chan []*sync.PathMapping, 1)}
|
||||
err := (&composeService{}).initialSync(t.Context(), types.ServiceConfig{
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{Context: hostDir, Dockerfile: "Dockerfile.prod"},
|
||||
Name: "svc",
|
||||
WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{Context: hostDir, Dockerfile: "Dockerfile.prod"}},
|
||||
}, types.Trigger{
|
||||
Path: hostDir,
|
||||
Target: "/app",
|
||||
|
|
@ -360,8 +359,8 @@ func TestInitialSync_ExcludesNestedCustomNamedDockerfile(t *testing.T) {
|
|||
|
||||
syncer := &fakeSyncer{synced: make(chan []*sync.PathMapping, 1)}
|
||||
err := (&composeService{}).initialSync(t.Context(), types.ServiceConfig{
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{Context: hostDir, Dockerfile: "docker/Dockerfile.prod"},
|
||||
Name: "svc",
|
||||
WorkloadSpec: types.WorkloadSpec{Build: &types.BuildConfig{Context: hostDir, Dockerfile: "docker/Dockerfile.prod"}},
|
||||
}, types.Trigger{
|
||||
Path: hostDir,
|
||||
Target: "/app",
|
||||
|
|
|
|||
|
|
@ -213,11 +213,11 @@ func TestConfig(t *testing.T) {
|
|||
OutputContains(fmt.Sprintf(`name: %s
|
||||
services:
|
||||
nginx:
|
||||
networks:
|
||||
default: null
|
||||
build:
|
||||
context: %s
|
||||
dockerfile: Dockerfile
|
||||
networks:
|
||||
default: null
|
||||
networks:
|
||||
default:
|
||||
name: %s_default
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue