project.Services is a map

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2023-11-27 10:14:31 +01:00 committed by Nicolas De loof
parent cda04f288e
commit 138facea62
25 changed files with 120 additions and 124 deletions

View File

@ -23,7 +23,6 @@ import (
"strings" "strings"
"github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
cliopts "github.com/docker/cli/opts" cliopts "github.com/docker/cli/opts"

View File

@ -26,20 +26,20 @@ import (
func TestFilterServices(t *testing.T) { func TestFilterServices(t *testing.T) {
p := &types.Project{ p := &types.Project{
Services: types.Services{ Services: types.Services{
{ "foo": {
Name: "foo", Name: "foo",
Links: []string{"bar"}, Links: []string{"bar"},
}, },
{ "bar": {
Name: "bar", Name: "bar",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{
"zot": {}, "zot": {},
}, },
}, },
{ "zot": {
Name: "zot", Name: "zot",
}, },
{ "qix": {
Name: "qix", Name: "qix",
}, },
}, },

View File

@ -86,7 +86,7 @@ func sampleProject() *types.Project {
return &types.Project{ return &types.Project{
Name: "test", Name: "test",
Services: types.Services{ Services: types.Services{
{ "svc": {
Name: "svc", Name: "svc",
Build: &types.BuildConfig{ Build: &types.BuildConfig{
Context: ".", Context: ".",

View File

@ -25,10 +25,7 @@ import (
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error { func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
defaultPlatform := project.Environment["DOCKER_DEFAULT_PLATFORM"] defaultPlatform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
for i := range project.Services { for _, service := range project.Services {
// mutable reference so platform fields can be updated
service := &project.Services[i]
if service.Build == nil { if service.Build == nil {
continue continue
} }

View File

@ -27,7 +27,7 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
makeProject := func() *types.Project { makeProject := func() *types.Project {
return &types.Project{ return &types.Project{
Services: types.Services{ Services: types.Services{
{ "test": {
Name: "test", Name: "test",
Image: "foo", Image: "foo",
Build: &types.BuildConfig{ Build: &types.BuildConfig{
@ -47,14 +47,14 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
t.Run("SinglePlatform", func(t *testing.T) { t.Run("SinglePlatform", func(t *testing.T) {
project := makeProject() project := makeProject()
require.NoError(t, applyPlatforms(project, true)) require.NoError(t, applyPlatforms(project, true))
require.EqualValues(t, []string{"alice/32"}, project.Services[0].Build.Platforms) require.EqualValues(t, []string{"alice/32"}, project.Services["test"].Build.Platforms)
}) })
t.Run("MultiPlatform", func(t *testing.T) { t.Run("MultiPlatform", func(t *testing.T) {
project := makeProject() project := makeProject()
require.NoError(t, applyPlatforms(project, false)) require.NoError(t, applyPlatforms(project, false))
require.EqualValues(t, []string{"linux/amd64", "linux/arm64", "alice/32"}, require.EqualValues(t, []string{"linux/amd64", "linux/arm64", "alice/32"},
project.Services[0].Build.Platforms) project.Services["test"].Build.Platforms)
}) })
} }
@ -65,7 +65,7 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
"DOCKER_DEFAULT_PLATFORM": "linux/amd64", "DOCKER_DEFAULT_PLATFORM": "linux/amd64",
}, },
Services: types.Services{ Services: types.Services{
{ "test": {
Name: "test", Name: "test",
Image: "foo", Image: "foo",
Build: &types.BuildConfig{ Build: &types.BuildConfig{
@ -83,14 +83,14 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
t.Run("SinglePlatform", func(t *testing.T) { t.Run("SinglePlatform", func(t *testing.T) {
project := makeProject() project := makeProject()
require.NoError(t, applyPlatforms(project, true)) require.NoError(t, applyPlatforms(project, true))
require.EqualValues(t, []string{"linux/amd64"}, project.Services[0].Build.Platforms) require.EqualValues(t, []string{"linux/amd64"}, project.Services["test"].Build.Platforms)
}) })
t.Run("MultiPlatform", func(t *testing.T) { t.Run("MultiPlatform", func(t *testing.T) {
project := makeProject() project := makeProject()
require.NoError(t, applyPlatforms(project, false)) require.NoError(t, applyPlatforms(project, false))
require.EqualValues(t, []string{"linux/amd64", "linux/arm64"}, require.EqualValues(t, []string{"linux/amd64", "linux/arm64"},
project.Services[0].Build.Platforms) project.Services["test"].Build.Platforms)
}) })
} }
@ -101,7 +101,7 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
"DOCKER_DEFAULT_PLATFORM": "commodore/64", "DOCKER_DEFAULT_PLATFORM": "commodore/64",
}, },
Services: types.Services{ Services: types.Services{
{ "foo": {
Name: "test", Name: "test",
Image: "foo", Image: "foo",
Build: &types.BuildConfig{ Build: &types.BuildConfig{

View File

@ -26,21 +26,21 @@ import (
func TestApplyPullOptions(t *testing.T) { func TestApplyPullOptions(t *testing.T) {
project := &types.Project{ project := &types.Project{
Services: types.Services{ Services: types.Services{
{ "must-build": {
Name: "must-build", Name: "must-build",
// No image, local build only // No image, local build only
Build: &types.BuildConfig{ Build: &types.BuildConfig{
Context: ".", Context: ".",
}, },
}, },
{ "has-build": {
Name: "has-build", Name: "has-build",
Image: "registry.example.com/myservice", Image: "registry.example.com/myservice",
Build: &types.BuildConfig{ Build: &types.BuildConfig{
Context: ".", Context: ".",
}, },
}, },
{ "must-pull": {
Name: "must-pull", Name: "must-pull",
Image: "registry.example.com/another-service", Image: "registry.example.com/another-service",
}, },
@ -51,7 +51,7 @@ func TestApplyPullOptions(t *testing.T) {
}.apply(project, nil) }.apply(project, nil)
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, project.Services[0].PullPolicy, "") // still default assert.Equal(t, project.Services["must-build"].PullPolicy, "") // still default
assert.Equal(t, project.Services[1].PullPolicy, types.PullPolicyMissing) assert.Equal(t, project.Services["has-build"].PullPolicy, types.PullPolicyMissing)
assert.Equal(t, project.Services[2].PullPolicy, types.PullPolicyMissing) assert.Equal(t, project.Services["must-pull"].PullPolicy, types.PullPolicyMissing)
} }

View File

@ -300,16 +300,16 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error { func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error {
dependencies := types.Services{} dependencies := types.Services{}
var requestedService types.ServiceConfig var requestedService types.ServiceConfig
for _, service := range project.Services { for name, service := range project.Services {
if service.Name != requestedServiceName { if service.Name != requestedServiceName {
dependencies = append(dependencies, service) dependencies[name] = service
} else { } else {
requestedService = service requestedService = service
} }
} }
project.Services = dependencies project.Services = dependencies
project.DisabledServices = append(project.DisabledServices, requestedService) project.DisabledServices[requestedServiceName] = requestedService
err := backend.Create(ctx, &project, api.CreateOptions{ err := backend.Create(ctx, &project, api.CreateOptions{
Build: buildOpts, Build: buildOpts,
IgnoreOrphans: ignoreOrphans, IgnoreOrphans: ignoreOrphans,

View File

@ -26,10 +26,10 @@ import (
func TestApplyScaleOpt(t *testing.T) { func TestApplyScaleOpt(t *testing.T) {
p := types.Project{ p := types.Project{
Services: types.Services{ Services: types.Services{
{ "foo": {
Name: "foo", Name: "foo",
}, },
{ "bar": {
Name: "bar", Name: "bar",
Deploy: &types.DeployConfig{ Deploy: &types.DeployConfig{
Mode: "test", Mode: "test",

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Microsoft/go-winio v0.6.1 github.com/Microsoft/go-winio v0.6.1
github.com/buger/goterm v1.0.4 github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.0.0-20231121074112-593b77722992 github.com/compose-spec/compose-go/v2 v2.0.0-20231123162526-11ef9572f1a4
github.com/containerd/console v1.0.3 github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.7.7 github.com/containerd/containerd v1.7.7
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1

4
go.sum
View File

@ -132,8 +132,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= 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/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.0.0-20231121074112-593b77722992 h1:0BM7GPtSRK7djjvG3h67aJYH8eRikBgxkrEG7wNtgaU= github.com/compose-spec/compose-go/v2 v2.0.0-20231123162526-11ef9572f1a4 h1:Lr78By808iuG+2gTyxIDslRpKQCk/lcRqElKsrhzp+U=
github.com/compose-spec/compose-go/v2 v2.0.0-20231121074112-593b77722992/go.mod h1:uAthZuC/GWStR8mxGMRaQyaOeSqA4V+MZIiAIfuBoIU= github.com/compose-spec/compose-go/v2 v2.0.0-20231123162526-11ef9572f1a4/go.mod h1:PWCgeD8cxiI/DmdpBM407CuLDrZ2W4xuS6/Z9jAi0YQ=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=

View File

@ -58,17 +58,12 @@ func ProjectOptions(proj *types.Project) SpanOptions {
return nil return nil
} }
disabledServiceNames := make([]string, len(proj.DisabledServices))
for i := range proj.DisabledServices {
disabledServiceNames[i] = proj.DisabledServices[i].Name
}
attrs := []attribute.KeyValue{ attrs := []attribute.KeyValue{
attribute.String("project.name", proj.Name), attribute.String("project.name", proj.Name),
attribute.String("project.dir", proj.WorkingDir), attribute.String("project.dir", proj.WorkingDir),
attribute.StringSlice("project.compose_files", proj.ComposeFiles), attribute.StringSlice("project.compose_files", proj.ComposeFiles),
attribute.StringSlice("project.services.active", proj.ServiceNames()), attribute.StringSlice("project.services.active", proj.ServiceNames()),
attribute.StringSlice("project.services.disabled", disabledServiceNames), attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
attribute.StringSlice("project.profiles", proj.Profiles), attribute.StringSlice("project.profiles", proj.Profiles),
attribute.StringSlice("project.volumes", proj.VolumeNames()), attribute.StringSlice("project.volumes", proj.VolumeNames()),
attribute.StringSlice("project.networks", proj.NetworkNames()), attribute.StringSlice("project.networks", proj.NetworkNames()),

View File

@ -67,7 +67,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
} }
type serviceToBuild struct { type serviceToBuild struct {
idx int name string
service types.ServiceConfig service types.ServiceConfig
} }
@ -85,7 +85,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if len(options.Services) > 0 && !utils.Contains(options.Services, name) { if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
return nil return nil
} }
service, idx := getServiceIndex(project, name) service := project.Services[name]
if service.Build == nil { if service.Build == nil {
return nil return nil
@ -97,7 +97,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
return nil return nil
} }
mapServiceMutx.Lock() mapServiceMutx.Lock()
serviceToBeBuild[name] = serviceToBuild{idx: idx, service: service} serviceToBeBuild[name] = serviceToBuild{name: name, service: service}
mapServiceMutx.Unlock() mapServiceMutx.Unlock()
return nil return nil
}, func(traversal *graphTraversal) { }, func(traversal *graphTraversal) {
@ -146,7 +146,17 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
} }
} }
// we use a pre-allocated []string to collect build digest by service index while running concurrent goroutines
builtDigests := make([]string, len(project.Services)) builtDigests := make([]string, len(project.Services))
names := project.ServiceNames()
getServiceIndex := func(name string) int {
for idx, n := range names {
if n == name {
return idx
}
}
return -1
}
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error { err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
if len(options.Services) > 0 && !utils.Contains(options.Services, name) { if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
return nil return nil
@ -156,14 +166,13 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
return nil return nil
} }
service := serviceToBuild.service service := serviceToBuild.service
idx := serviceToBuild.idx
if !buildkitEnabled { if !buildkitEnabled {
id, err := s.doBuildClassic(ctx, project, service, options) id, err := s.doBuildClassic(ctx, project, service, options)
if err != nil { if err != nil {
return err return err
} }
builtDigests[idx] = id builtDigests[getServiceIndex(name)] = id
if options.Push { if options.Push {
return s.push(ctx, project, api.PushOptions{}) return s.push(ctx, project, api.PushOptions{})
@ -184,7 +193,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if err != nil { if err != nil {
return err return err
} }
builtDigests[idx] = digest builtDigests[getServiceIndex(name)] = digest
return nil return nil
}, func(traversal *graphTraversal) { }, func(traversal *graphTraversal) {
@ -204,25 +213,13 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
for i, imageDigest := range builtDigests { for i, imageDigest := range builtDigests {
if imageDigest != "" { if imageDigest != "" {
imageRef := api.GetImageNameOrDefault(project.Services[i], project.Name) imageRef := api.GetImageNameOrDefault(project.Services[names[i]], project.Name)
imageIDs[imageRef] = imageDigest imageIDs[imageRef] = imageDigest
} }
} }
return imageIDs, err return imageIDs, err
} }
func getServiceIndex(project *types.Project, name string) (types.ServiceConfig, int) {
var service types.ServiceConfig
var idx int
for i, s := range project.Services {
if s.Name == name {
idx, service = i, s
break
}
}
return service, idx
}
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error { func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
for _, service := range project.Services { for _, service := range project.Services {
if service.Image == "" && service.Build == nil { if service.Image == "" && service.Build == nil {
@ -264,14 +261,14 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
} }
// set digest as com.docker.compose.image label so we can detect outdated containers // set digest as com.docker.compose.image label so we can detect outdated containers
for i, service := range project.Services { for _, service := range project.Services {
image := api.GetImageNameOrDefault(service, project.Name) image := api.GetImageNameOrDefault(service, project.Name)
digest, ok := images[image] digest, ok := images[image]
if ok { if ok {
if project.Services[i].Labels == nil { if service.Labels == nil {
project.Services[i].Labels = types.Labels{} service.Labels = types.Labels{}
} }
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest) service.CustomLabels.Add(api.ImageDigestLabel, digest)
} }
} }
return nil return nil
@ -440,7 +437,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
Platforms: plats, Platforms: plats,
Labels: imageLabels, Labels: imageLabels,
NetworkMode: service.Build.Network, NetworkMode: service.Build.Network,
ExtraHosts: service.Build.ExtraHosts.AsList(), ExtraHosts: service.Build.ExtraHosts.AsList(":"),
Ulimits: toUlimitOpt(service.Build.Ulimits), Ulimits: toUlimitOpt(service.Build.Ulimits),
Session: sessionConfig, Session: sessionConfig,
Allow: allow, Allow: allow,

View File

@ -229,7 +229,7 @@ func imageBuildOptions(dockerCli command.Cli, project *types.Project, service ty
BuildArgs: resolveAndMergeBuildArgs(dockerCli, project, service, options), BuildArgs: resolveAndMergeBuildArgs(dockerCli, project, service, options),
Labels: config.Labels, Labels: config.Labels,
NetworkMode: config.Network, NetworkMode: config.Network,
ExtraHosts: config.ExtraHosts.AsList(), ExtraHosts: config.ExtraHosts.AsList(":"),
Target: config.Target, Target: config.Target,
Isolation: container.Isolation(config.Isolation), Isolation: container.Isolation(config.Isolation),
} }

View File

@ -183,7 +183,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
if len(containers) == 0 { if len(containers) == 0 {
return project, fmt.Errorf("no container found for project %q: %w", projectName, api.ErrNotFound) return project, fmt.Errorf("no container found for project %q: %w", projectName, api.ErrNotFound)
} }
set := map[string]types.ServiceConfig{} set := types.Services{}
for _, c := range containers { for _, c := range containers {
serviceLabel := c.Labels[api.ServiceLabel] serviceLabel := c.Labels[api.ServiceLabel]
service, ok := set[serviceLabel] service, ok := set[serviceLabel]
@ -197,7 +197,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
} }
service.Scale = increment(service.Scale) service.Scale = increment(service.Scale)
} }
for _, service := range set { for name, service := range set {
dependencies := service.Labels[api.DependenciesLabel] dependencies := service.Labels[api.DependenciesLabel]
if len(dependencies) > 0 { if len(dependencies) > 0 {
service.DependsOn = types.DependsOnConfig{} service.DependsOn = types.DependsOnConfig{}
@ -218,9 +218,11 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
} }
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition, Restart: restart, Required: required} service.DependsOn[dependency] = types.ServiceDependency{Condition: condition, Restart: restart, Required: required}
} }
set[name] = service
} }
project.Services = append(project.Services, service)
} }
project.Services = set
SERVICES: SERVICES:
for _, qs := range services { for _, qs := range services {
for _, es := range project.Services { for _, es := range project.Services {

View File

@ -227,7 +227,10 @@ func TestWaitDependencies(t *testing.T) {
t.Run("should skip dependencies with scale 0", func(t *testing.T) { t.Run("should skip dependencies with scale 0", func(t *testing.T) {
dbService := types.ServiceConfig{Name: "db", Scale: intPtr(0)} dbService := types.ServiceConfig{Name: "db", Scale: intPtr(0)}
redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(0)} redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(0)}
project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{dbService, redisService}} project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
"db": dbService,
"redis": redisService,
}}
dependencies := types.DependsOnConfig{ dependencies := types.DependsOnConfig{
"db": {Condition: ServiceConditionRunningOrHealthy}, "db": {Condition: ServiceConditionRunningOrHealthy},
"redis": {Condition: ServiceConditionRunningOrHealthy}, "redis": {Condition: ServiceConditionRunningOrHealthy},
@ -237,7 +240,10 @@ func TestWaitDependencies(t *testing.T) {
t.Run("should skip dependencies with condition service_started", func(t *testing.T) { t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
dbService := types.ServiceConfig{Name: "db", Scale: intPtr(1)} dbService := types.ServiceConfig{Name: "db", Scale: intPtr(1)}
redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(1)} redisService := types.ServiceConfig{Name: "redis", Scale: intPtr(1)}
project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{dbService, redisService}} project := types.Project{Name: strings.ToLower(testProject), Services: types.Services{
"db": dbService,
"redis": redisService,
}}
dependencies := types.DependsOnConfig{ dependencies := types.DependsOnConfig{
"db": {Condition: types.ServiceConditionStarted, Required: true}, "db": {Condition: types.ServiceConditionStarted, Required: true},
"redis": {Condition: types.ServiceConditionStarted, Required: true}, "redis": {Condition: types.ServiceConditionStarted, Required: true},

View File

@ -94,11 +94,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
return err return err
} }
allServices := project.AllServices() allServiceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
allServiceNames := []string{}
for _, service := range allServices {
allServiceNames = append(allServiceNames, service.Name)
}
orphans := observedState.filter(isNotService(allServiceNames...)) orphans := observedState.filter(isNotService(allServiceNames...))
if len(orphans) > 0 && !options.IgnoreOrphans { if len(orphans) > 0 && !options.IgnoreOrphans {
if options.RemoveOrphans { if options.RemoveOrphans {
@ -263,7 +259,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
DNS: service.DNS, DNS: service.DNS,
DNSSearch: service.DNSSearch, DNSSearch: service.DNSSearch,
DNSOptions: service.DNSOpts, DNSOptions: service.DNSOpts,
ExtraHosts: service.ExtraHosts.AsList(), ExtraHosts: service.ExtraHosts.AsList(":"),
SecurityOpt: securityOpts, SecurityOpt: securityOpts,
UsernsMode: container.UsernsMode(service.UserNSMode), UsernsMode: container.UsernsMode(service.UserNSMode),
UTSMode: container.UTSMode(service.Uts), UTSMode: container.UTSMode(service.Uts),

View File

@ -101,8 +101,8 @@ func TestPrepareNetworkLabels(t *testing.T) {
func TestBuildContainerMountOptions(t *testing.T) { func TestBuildContainerMountOptions(t *testing.T) {
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Services: []composetypes.ServiceConfig{ Services: composetypes.Services{
{ "myService": {
Name: "myService", Name: "myService",
Volumes: []composetypes.ServiceVolumeConfig{ Volumes: []composetypes.ServiceVolumeConfig{
{ {
@ -144,7 +144,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
}, },
} }
mounts, err := buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit) mounts, err := buildContainerMountOptions(project, project.Services["myService"], moby.ImageInspect{}, inherit)
sort.Slice(mounts, func(i, j int) bool { sort.Slice(mounts, func(i, j int) bool {
return mounts[i].Target < mounts[j].Target return mounts[i].Target < mounts[j].Target
}) })
@ -154,7 +154,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
assert.Equal(t, mounts[1].Target, "/var/myvolume2") assert.Equal(t, mounts[1].Target, "/var/myvolume2")
assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine") assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit) mounts, err = buildContainerMountOptions(project, project.Services["myService"], moby.ImageInspect{}, inherit)
sort.Slice(mounts, func(i, j int) bool { sort.Slice(mounts, func(i, j int) bool {
return mounts[i].Target < mounts[j].Target return mounts[i].Target < mounts[j].Target
}) })
@ -180,8 +180,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
} }
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Services: []composetypes.ServiceConfig{ Services: composetypes.Services{
service, "myService": service,
}, },
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{ Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
"myNetwork1": { "myNetwork1": {
@ -205,8 +205,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
} }
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Services: []composetypes.ServiceConfig{ Services: composetypes.Services{
service, "myService": service,
}, },
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{ Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
"myNetwork1": { "myNetwork1": {
@ -233,8 +233,8 @@ func TestDefaultNetworkSettings(t *testing.T) {
} }
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Services: []composetypes.ServiceConfig{ Services: composetypes.Services{
service, "myService": service,
}, },
} }
@ -250,7 +250,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
} }
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Services: []composetypes.ServiceConfig{service}, Services: composetypes.Services{"myService": service},
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{ Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
"default": { "default": {
Name: "myProject_default", Name: "myProject_default",

View File

@ -33,19 +33,19 @@ import (
func createTestProject() *types.Project { func createTestProject() *types.Project {
return &types.Project{ return &types.Project{
Services: types.Services{ Services: types.Services{
{ "test1": {
Name: "test1", Name: "test1",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{
"test2": {}, "test2": {},
}, },
}, },
{ "test2": {
Name: "test2", Name: "test2",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{
"test3": {}, "test3": {},
}, },
}, },
{ "test3": {
Name: "test3", Name: "test3",
}, },
}, },
@ -59,7 +59,7 @@ func TestTraversalWithMultipleParents(t *testing.T) {
} }
project := types.Project{ project := types.Project{
Services: types.Services{dependent}, Services: types.Services{"dependent": dependent},
} }
for i := 1; i <= 100; i++ { for i := 1; i <= 100; i++ {
@ -67,7 +67,7 @@ func TestTraversalWithMultipleParents(t *testing.T) {
dependent.DependsOn[name] = types.ServiceDependency{} dependent.DependsOn[name] = types.ServiceDependency{}
svc := types.ServiceConfig{Name: name} svc := types.ServiceConfig{Name: name}
project.Services = append(project.Services, svc) project.Services[name] = svc
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -132,7 +132,7 @@ func TestBuildGraph(t *testing.T) {
{ {
desc: "builds graph with single service", desc: "builds graph with single service",
services: types.Services{ services: types.Services{
{ "test": {
Name: "test", Name: "test",
DependsOn: types.DependsOnConfig{}, DependsOn: types.DependsOnConfig{},
}, },
@ -150,11 +150,11 @@ func TestBuildGraph(t *testing.T) {
{ {
desc: "builds graph with two separate services", desc: "builds graph with two separate services",
services: types.Services{ services: types.Services{
{ "test": {
Name: "test", Name: "test",
DependsOn: types.DependsOnConfig{}, DependsOn: types.DependsOnConfig{},
}, },
{ "another": {
Name: "another", Name: "another",
DependsOn: types.DependsOnConfig{}, DependsOn: types.DependsOnConfig{},
}, },
@ -179,13 +179,13 @@ func TestBuildGraph(t *testing.T) {
{ {
desc: "builds graph with a service and a dependency", desc: "builds graph with a service and a dependency",
services: types.Services{ services: types.Services{
{ "test": {
Name: "test", Name: "test",
DependsOn: types.DependsOnConfig{ DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{}, "another": types.ServiceDependency{},
}, },
}, },
{ "another": {
Name: "another", Name: "another",
DependsOn: types.DependsOnConfig{}, DependsOn: types.DependsOnConfig{},
}, },
@ -214,19 +214,19 @@ func TestBuildGraph(t *testing.T) {
{ {
desc: "builds graph with multiple dependency levels", desc: "builds graph with multiple dependency levels",
services: types.Services{ services: types.Services{
{ "test": {
Name: "test", Name: "test",
DependsOn: types.DependsOnConfig{ DependsOn: types.DependsOnConfig{
"another": types.ServiceDependency{}, "another": types.ServiceDependency{},
}, },
}, },
{ "another": {
Name: "another", Name: "another",
DependsOn: types.DependsOnConfig{ DependsOn: types.DependsOnConfig{
"another_dep": types.ServiceDependency{}, "another_dep": types.ServiceDependency{},
}, },
}, },
{ "another_dep": {
Name: "another_dep", Name: "another_dep",
DependsOn: types.DependsOnConfig{}, DependsOn: types.DependsOnConfig{},
}, },

View File

@ -184,12 +184,12 @@ func TestDownRemoveImages(t *testing.T) {
Project: &types.Project{ Project: &types.Project{
Name: strings.ToLower(testProject), Name: strings.ToLower(testProject),
Services: types.Services{ Services: types.Services{
{Name: "local-anonymous"}, "local-anonymous": {Name: "local-anonymous"},
{Name: "local-named", Image: "local-named-image"}, "local-named": {Name: "local-named", Image: "local-named-image"},
{Name: "remote", Image: "remote-image"}, "remote": {Name: "remote", Image: "remote-image"},
{Name: "remote-tagged", Image: "registry.example.com/remote-image-tagged:v1.0"}, "remote-tagged": {Name: "remote-tagged", Image: "registry.example.com/remote-image-tagged:v1.0"},
{Name: "no-images-anonymous"}, "no-images-anonymous": {Name: "no-images-anonymous"},
{Name: "no-images-named", Image: "missing-named-image"}, "no-images-named": {Name: "no-images-named", Image: "missing-named-image"},
}, },
}, },
} }

View File

@ -83,7 +83,7 @@ func TestComposeService_Logs_Demux(t *testing.T) {
opts := compose.LogOptions{ opts := compose.LogOptions{
Project: &types.Project{ Project: &types.Project{
Services: types.Services{ Services: types.Services{
{Name: "service"}, "service": {Name: "service"},
}, },
}, },
} }
@ -153,8 +153,8 @@ func TestComposeService_Logs_ServiceFiltering(t *testing.T) {
// reference `serviceB` even though it has running services for this proj // reference `serviceB` even though it has running services for this proj
proj := &types.Project{ proj := &types.Project{
Services: types.Services{ Services: types.Services{
{Name: "serviceA"}, "serviceA": {Name: "serviceA"},
{Name: "serviceC"}, "serviceC": {Name: "serviceC"},
}, },
} }
consumer := &testLogConsumer{} consumer := &testLogConsumer{}

View File

@ -63,8 +63,8 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
imagesBeingPulled = map[string]string{} imagesBeingPulled = map[string]string{}
) )
for i, service := range project.Services { i := 0
i, service := i, service for _, service := range project.Services {
if service.Image == "" { if service.Image == "" {
w.Event(progress.Event{ w.Event(progress.Event{
ID: service.Name, ID: service.Name,
@ -113,10 +113,11 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
imagesBeingPulled[service.Image] = service.Name imagesBeingPulled[service.Image] = service.Name
idx, service := i, service
eg.Go(func() error { eg.Go(func() error {
_, err := s.pullServiceImage(ctx, service, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"]) _, err := s.pullServiceImage(ctx, service, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"])
if err != nil { if err != nil {
pullErrors[i] = err pullErrors[idx] = err
if service.Build != nil { if service.Build != nil {
mustBuild = append(mustBuild, service.Name) mustBuild = append(mustBuild, service.Name)
} }
@ -134,6 +135,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
} }
return nil return nil
}) })
i++
} }
err = eg.Wait() err = eg.Wait()
@ -260,7 +262,7 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
} }
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error { func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
var needPull types.Services var needPull []types.ServiceConfig
for _, service := range project.Services { for _, service := range project.Services {
if service.Image == "" { if service.Image == "" {
continue continue

View File

@ -34,7 +34,7 @@ func TestViz(t *testing.T) {
Name: "viz-test", Name: "viz-test",
WorkingDir: "/home", WorkingDir: "/home",
Services: types.Services{ Services: types.Services{
{ "service1": {
Name: "service1", Name: "service1",
Image: "image-for-service1", Image: "image-for-service1",
Ports: []types.ServicePortConfig{ Ports: []types.ServicePortConfig{
@ -53,12 +53,12 @@ func TestViz(t *testing.T) {
"internal": nil, "internal": nil,
}, },
}, },
{ "service2": {
Name: "service2", Name: "service2",
Image: "image-for-service2", Image: "image-for-service2",
Ports: []types.ServicePortConfig{}, Ports: []types.ServicePortConfig{},
}, },
{ "service3": {
Name: "service3", Name: "service3",
Image: "some-image", Image: "some-image",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{
@ -66,7 +66,7 @@ func TestViz(t *testing.T) {
"service1": {}, "service1": {},
}, },
}, },
{ "service4": {
Name: "service4", Name: "service4",
Image: "another-image", Image: "another-image",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{
@ -82,7 +82,7 @@ func TestViz(t *testing.T) {
"external": nil, "external": nil,
}, },
}, },
{ "With host IP": {
Name: "With host IP", Name: "With host IP",
Image: "user/image-name", Image: "user/image-name",
DependsOn: map[string]types.ServiceDependency{ DependsOn: map[string]types.ServiceDependency{

View File

@ -106,7 +106,7 @@ func TestWatch_Sync(t *testing.T) {
proj := types.Project{ proj := types.Project{
Services: types.Services{ Services: types.Services{
{ "test": {
Name: "test", Name: "test",
}, },
}, },

View File

@ -79,10 +79,10 @@ func TestRestartWithDependencies(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/restart-test/compose-depends-on.yaml", "up", "-d") c.RunDockerComposeCmd(t, "-f", "./fixtures/restart-test/compose-depends-on.yaml", "up", "-d")
res := c.RunDockerComposeCmd(t, "restart", baseService) res := c.RunDockerComposeCmd(t, "restart", baseService)
fmt.Println(res.Combined()) out := res.Combined()
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", baseService)), res.Combined()) assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", baseService)), out)
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), res.Combined()) assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), out)
assert.Assert(t, !strings.Contains(res.Combined(), depNoRestart), res.Combined()) assert.Assert(t, !strings.Contains(out, depNoRestart), out)
} }
func TestRestartWithProfiles(t *testing.T) { func TestRestartWithProfiles(t *testing.T) {

View File

@ -106,12 +106,14 @@ func TestStartStopWithDependencies(t *testing.T) {
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Stopped"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Stopped"), res.Combined())
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "start", "foo") res = c.RunDockerComposeCmd(t, "--project-name", projectName, "start", "foo")
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Started"), res.Combined()) out := res.Combined()
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-foo-1 Started"), res.Combined()) assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-bar-1 Started"), out)
assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-foo-1 Started"), out)
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--status", "running") res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--status", "running")
assert.Assert(t, strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-bar-1"), res.Combined()) out = res.Combined()
assert.Assert(t, strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-foo-1"), res.Combined()) assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-bar-1"), out)
assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-foo-1"), out)
}) })
t.Run("Up no-deps links", func(t *testing.T) { t.Run("Up no-deps links", func(t *testing.T) {