diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index 1b6335632..5bb63cd9d 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -69,7 +69,7 @@ func (o *projectOptions) toProject(services []string) (*types.Project, error) { } if len(services) != 0 { - s, err := project.GetServices(services) + s, err := project.GetServices(services...) if err != nil { return nil, err } diff --git a/cli/cmd/compose/compose_test.go b/cli/cmd/compose/compose_test.go index dfa68cf45..b19404424 100644 --- a/cli/cmd/compose/compose_test.go +++ b/cli/cmd/compose/compose_test.go @@ -32,7 +32,7 @@ func TestFilterServices(t *testing.T) { }, { Name: "bar", - NetworkMode: "service:zot", + NetworkMode: types.NetworkModeServicePrefix + "zot", }, { Name: "zot", diff --git a/go.mod b/go.mod index 6926a55c0..808e446ea 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/awslabs/goformation/v4 v4.15.6 github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 github.com/cnabio/cnab-to-oci v0.3.1-beta1 - github.com/compose-spec/compose-go v0.0.0-20210217144939-9f2c61fe6b14 + github.com/compose-spec/compose-go v0.0.0-20210218184709-a75bbdcff7f3 github.com/containerd/console v1.0.1 github.com/containerd/containerd v1.4.3 github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect diff --git a/go.sum b/go.sum index 1aff162b0..2e4134342 100644 --- a/go.sum +++ b/go.sum @@ -302,8 +302,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/compose-spec/compose-go v0.0.0-20210217144939-9f2c61fe6b14 h1:ezR3VeA9GLPRMXYC0JzuHOM6c9yHMbc6EHaZy6lEKRA= -github.com/compose-spec/compose-go v0.0.0-20210217144939-9f2c61fe6b14/go.mod h1:flNthwF3kg+JioxATZWSsuuA2N3zGGwggDNNGoE9PHA= +github.com/compose-spec/compose-go v0.0.0-20210218184709-a75bbdcff7f3 h1:cGJa3EMDcclDU21e/CVQJnDf3ZjnB6HN9TvxkFHpGq8= +github.com/compose-spec/compose-go v0.0.0-20210218184709-a75bbdcff7f3/go.mod h1:flNthwF3kg+JioxATZWSsuuA2N3zGGwggDNNGoE9PHA= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw= diff --git a/local/compose/convergence.go b/local/compose/convergence.go index 5ba389409..97fa40fc1 100644 --- a/local/compose/convergence.go +++ b/local/compose/convergence.go @@ -42,7 +42,17 @@ const ( "Remove the custom name to scale the service.\n" ) -func (s *composeService) ensureScale(ctx context.Context, actual []moby.Container, scale int, project *types.Project, service types.ServiceConfig) (*errgroup.Group, []moby.Container, error) { +func (s *composeService) ensureScale(ctx context.Context, project *types.Project, service types.ServiceConfig) (*errgroup.Group, []moby.Container, error) { + cState, err := GetContextContainerState(ctx) + if err != nil { + return nil, nil, err + } + observedState := cState.GetContainers() + actual := observedState.filter(isService(service.Name)) + scale, err := getScale(service) + if err != nil { + return nil, nil, err + } eg, _ := errgroup.WithContext(ctx) if len(actual) < scale { next, err := nextContainerNumber(actual) @@ -75,15 +85,8 @@ func (s *composeService) ensureScale(ctx context.Context, actual []moby.Containe return eg, actual, nil } -func (s *composeService) ensureService(ctx context.Context, observedState Containers, project *types.Project, service types.ServiceConfig, recreate string) error { - actual := observedState.filter(isService(service.Name)) - - scale, err := getScale(service) - if err != nil { - return err - } - - eg, actual, err := s.ensureScale(ctx, actual, scale, project, service) +func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string) error { + eg, actual, err := s.ensureScale(ctx, project, service) if err != nil { return err } @@ -260,7 +263,12 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co return nil } -func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container, autoRemove bool) error { +func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container, + autoRemove bool) error { + cState, err := GetContextContainerState(ctx) + if err != nil { + return err + } containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, container, autoRemove) if err != nil { return err @@ -269,10 +277,14 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types if err != nil { return err } - id := created.ID + createdContainer := moby.Container{ + ID: created.ID, + Labels: containerConfig.Labels, + } + cState.Add(createdContainer) for netName := range service.Networks { netwrk := project.Networks[netName] - err = s.connectContainerToNetwork(ctx, id, netwrk.Name, service.Name, getContainerName(project.Name, service, number)) + err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, service.Name, getContainerName(project.Name, service, number)) if err != nil { return err } diff --git a/local/compose/create.go b/local/compose/create.go index 5b0e121b5..13cd307a0 100644 --- a/local/compose/create.go +++ b/local/compose/create.go @@ -70,6 +70,8 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt if err != nil { return err } + containerState := NewContainersState(observedState) + ctx = context.WithValue(ctx, ContainersKey{}, containerState) allServices := project.AllServices() allServiceNames := []string{} @@ -92,8 +94,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt } } + prepareNetworkMode(project) + return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { - return s.ensureService(c, observedState, project, service, opts.Recreate) + return s.ensureService(c, project, service, opts.Recreate) }) } @@ -129,6 +133,27 @@ func prepareNetworks(project *types.Project) { } } +func prepareNetworkMode(p *types.Project) { +outLoop: + for i := range p.Services { + dependency := getDependentServiceByNetwork(p.Services[i].NetworkMode) + if dependency == "" { + continue + } + if p.Services[i].DependsOn == nil { + p.Services[i].DependsOn = make(types.DependsOnConfig) + } + for _, service := range p.Services { + if service.Name == dependency { + p.Services[i].DependsOn[service.Name] = types.ServiceDependency{ + Condition: types.ServiceConditionStarted, + } + continue outLoop + } + } + } +} + func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error { for _, network := range networks { err := s.ensureNetwork(ctx, network) @@ -235,7 +260,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, portBindings := buildContainerPortBindingOptions(service) resources := getDeployResources(service) - networkMode := getNetworkMode(p, service) + + networkMode, err := getNetworkMode(ctx, p, service) + if err != nil { + return nil, nil, nil, err + } hostConfig := container.HostConfig{ AutoRemove: autoRemove, Binds: binds, @@ -335,6 +364,14 @@ func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []s } +func getDependentServiceByNetwork(networkMode string) string { + baseService := "" + if strings.HasPrefix(networkMode, types.NetworkModeServicePrefix) { + return networkMode[len(types.NetworkModeServicePrefix):] + } + return baseService +} + func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig, inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) { var mounts = []mount.Mount{} @@ -602,32 +639,42 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string { return aliases } -func getNetworkMode(p *types.Project, service types.ServiceConfig) container.NetworkMode { +func getNetworkMode(ctx context.Context, p *types.Project, service types.ServiceConfig) (container.NetworkMode, error) { + cState, err := GetContextContainerState(ctx) + if err != nil { + return container.NetworkMode("none"), nil + } + observedState := cState.GetContainers() + mode := service.NetworkMode if mode == "" { if len(p.Networks) > 0 { for name := range getNetworksForService(service) { - return container.NetworkMode(p.Networks[name].Name) + return container.NetworkMode(p.Networks[name].Name), nil } } - return container.NetworkMode("none") + return container.NetworkMode("none"), nil } - - // FIXME incomplete implementation - if strings.HasPrefix(mode, "service:") { - panic("Not yet implemented") + depServiceNetworkMode := getDependentServiceByNetwork(service.NetworkMode) + if depServiceNetworkMode != "" { + depServiceContainers := observedState.filter(isService(depServiceNetworkMode)) + if len(depServiceContainers) > 0 { + return container.NetworkMode(types.NetworkModeContainerPrefix + depServiceContainers[0].ID), nil + } + return container.NetworkMode("none"), + fmt.Errorf(`no containers started for network_mode %q in service %q -> %v`, + mode, service.Name, observedState) } - if strings.HasPrefix(mode, "container:") { - panic("Not yet implemented") - } - - return container.NetworkMode(mode) + return container.NetworkMode(mode), nil } func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig { if len(s.Networks) > 0 { return s.Networks } + if s.NetworkMode != "" { + return nil + } return map[string]*types.ServiceNetworkConfig{"default": nil} } diff --git a/local/compose/run.go b/local/compose/run.go index 9f093d4de..d29f07f12 100644 --- a/local/compose/run.go +++ b/local/compose/run.go @@ -30,6 +30,16 @@ import ( ) func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) { + observedState, err := s.apiClient.ContainerList(ctx, apitypes.ContainerListOptions{ + Filters: filters.NewArgs(projectFilter(project.Name)), + All: true, + }) + if err != nil { + return 0, err + } + containerState := NewContainersState(observedState) + ctx = context.WithValue(ctx, ContainersKey{}, containerState) + service, err := project.GetService(opts.Service) if err != nil { return 0, err diff --git a/local/compose/status.go b/local/compose/status.go new file mode 100644 index 000000000..854b1918d --- /dev/null +++ b/local/compose/status.go @@ -0,0 +1,102 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + + "github.com/docker/docker/api/types" + "github.com/pkg/errors" +) + +// ContainersKey is the context key to access context value os a ContainersStatus +type ContainersKey struct{} + +// ContainersState state management interface +type ContainersState interface { + Get(string) *types.Container + GetContainers() Containers + Add(c types.Container) + AddAll(cs Containers) + Remove(string) types.Container +} + +// NewContainersState creates a new container state manager +func NewContainersState(cs Containers) ContainersState { + s := containersState{ + observedContainers: &cs, + } + return &s +} + +// ContainersStatus works as a collection container for the observed containers +type containersState struct { + observedContainers *Containers +} + +func (s *containersState) AddAll(cs Containers) { + for _, c := range cs { + lValue := append(*s.observedContainers, c) + s.observedContainers = &lValue + } +} + +func (s *containersState) Add(c types.Container) { + if s.Get(c.ID) == nil { + lValue := append(*s.observedContainers, c) + s.observedContainers = &lValue + } +} + +func (s *containersState) Remove(id string) types.Container { + var c types.Container + var newObserved Containers + for _, o := range *s.observedContainers { + if o.ID != id { + c = o + continue + } + newObserved = append(newObserved, o) + } + s.observedContainers = &newObserved + return c +} + +func (s *containersState) Get(id string) *types.Container { + for _, o := range *s.observedContainers { + if id == o.ID { + return &o + } + } + return nil +} + +func (s *containersState) GetContainers() Containers { + if s.observedContainers != nil && *s.observedContainers != nil { + return *s.observedContainers + } + return make(Containers, 0) +} + +// GetContextContainerState gets the container state manager +func GetContextContainerState(ctx context.Context) (ContainersState, error) { + cState, ok := ctx.Value(ContainersKey{}).(*containersState) + if !ok { + return nil, errors.New("containers' containersState not available in context") + } + return cState, nil +} diff --git a/local/e2e/compose/fixtures/network-test/compose.yaml b/local/e2e/compose/fixtures/network-test/compose.yaml index e3435fe32..bb49f318b 100644 --- a/local/e2e/compose/fixtures/network-test/compose.yaml +++ b/local/e2e/compose/fixtures/network-test/compose.yaml @@ -1,4 +1,9 @@ services: + mydb: + image: mysql + network_mode: "service:db" + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes db: image: gtardif/sentences-db networks: