mirror of
https://github.com/docker/compose.git
synced 2025-07-23 05:34:36 +02:00
don't rely on depends_on to resolve volume_from, better use observed state
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
e19232e8a3
commit
f6e31dbc6a
@ -178,6 +178,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
|||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
err = c.resolveVolumeFrom(&service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
sort.Slice(containers, func(i, j int) bool {
|
sort.Slice(containers, func(i, j int) bool {
|
||||||
return containers[i].Created < containers[j].Created
|
return containers[i].Created < containers[j].Created
|
||||||
})
|
})
|
||||||
@ -258,6 +263,26 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *convergence) resolveVolumeFrom(service *types.ServiceConfig) error {
|
||||||
|
for i, vol := range service.VolumesFrom {
|
||||||
|
spec := strings.Split(vol, ":")
|
||||||
|
if len(spec) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if spec[0] == "container" {
|
||||||
|
service.VolumesFrom[i] = spec[1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := spec[0]
|
||||||
|
dependencies := c.getObservedState(name)
|
||||||
|
if len(dependencies) == 0 {
|
||||||
|
return fmt.Errorf("cannot share volume with service %s: container missing", name)
|
||||||
|
}
|
||||||
|
service.VolumesFrom[i] = dependencies.sorted()[0].ID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
||||||
if policy == api.RecreateNever {
|
if policy == api.RecreateNever {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -86,11 +86,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
|||||||
|
|
||||||
prepareNetworks(project)
|
prepareNetworks(project)
|
||||||
|
|
||||||
err = prepareVolumes(project)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -123,31 +118,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
|||||||
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
|
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareVolumes(p *types.Project) error {
|
|
||||||
for i := range p.Services {
|
|
||||||
volumesFrom, dependServices, err := getVolumesFrom(p, p.Services[i].VolumesFrom)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Services[i].VolumesFrom = volumesFrom
|
|
||||||
if len(dependServices) > 0 {
|
|
||||||
if p.Services[i].DependsOn == nil {
|
|
||||||
p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
|
|
||||||
}
|
|
||||||
for _, service := range p.Services {
|
|
||||||
if utils.StringContains(dependServices, service.Name) &&
|
|
||||||
p.Services[i].DependsOn[service.Name].Condition == "" {
|
|
||||||
p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
|
|
||||||
Condition: types.ServiceConditionStarted,
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareNetworks(project *types.Project) {
|
func prepareNetworks(project *types.Project) {
|
||||||
for k, network := range project.Networks {
|
for k, network := range project.Networks {
|
||||||
network.Labels = network.Labels.Add(api.NetworkLabel, k)
|
network.Labels = network.Labels.Add(api.NetworkLabel, k)
|
||||||
@ -249,13 +219,6 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return createConfigs{}, err
|
return createConfigs{}, err
|
||||||
}
|
}
|
||||||
var volumesFrom []string
|
|
||||||
for _, v := range service.VolumesFrom {
|
|
||||||
if !strings.HasPrefix(v, "container:") {
|
|
||||||
return createConfigs{}, fmt.Errorf("invalid volume_from: %s", v)
|
|
||||||
}
|
|
||||||
volumesFrom = append(volumesFrom, v[len("container:"):])
|
|
||||||
}
|
|
||||||
|
|
||||||
// NETWORKING
|
// NETWORKING
|
||||||
links, err := s.getLinks(ctx, p.Name, service, number)
|
links, err := s.getLinks(ctx, p.Name, service, number)
|
||||||
@ -296,7 +259,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
|
|||||||
PortBindings: portBindings,
|
PortBindings: portBindings,
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
VolumeDriver: service.VolumeDriver,
|
VolumeDriver: service.VolumeDriver,
|
||||||
VolumesFrom: volumesFrom,
|
VolumesFrom: service.VolumesFrom,
|
||||||
DNS: service.DNS,
|
DNS: service.DNS,
|
||||||
DNSSearch: service.DNSSearch,
|
DNSSearch: service.DNSSearch,
|
||||||
DNSOptions: service.DNSOpts,
|
DNSOptions: service.DNSOpts,
|
||||||
@ -676,40 +639,6 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
|
|||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
|
|
||||||
var volumes = []string{}
|
|
||||||
var services = []string{}
|
|
||||||
// parse volumes_from
|
|
||||||
if len(volumesFrom) == 0 {
|
|
||||||
return volumes, services, nil
|
|
||||||
}
|
|
||||||
for _, vol := range volumesFrom {
|
|
||||||
spec := strings.Split(vol, ":")
|
|
||||||
if len(spec) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if spec[0] == "container" {
|
|
||||||
volumes = append(volumes, vol)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
serviceName := spec[0]
|
|
||||||
services = append(services, serviceName)
|
|
||||||
service, err := project.GetService(serviceName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
firstContainer := getContainerName(project.Name, service, 1)
|
|
||||||
v := fmt.Sprintf("container:%s", firstContainer)
|
|
||||||
if len(spec) > 2 {
|
|
||||||
v = fmt.Sprintf("container:%s:%s", firstContainer, strings.Join(spec[1:], ":"))
|
|
||||||
}
|
|
||||||
volumes = append(volumes, v)
|
|
||||||
}
|
|
||||||
return volumes, services, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDependentServiceFromMode(mode string) string {
|
func getDependentServiceFromMode(mode string) string {
|
||||||
if strings.HasPrefix(
|
if strings.HasPrefix(
|
||||||
mode,
|
mode,
|
||||||
|
@ -98,46 +98,6 @@ func TestPrepareNetworkLabels(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrepareVolumes(t *testing.T) {
|
|
||||||
t.Run("adds dependency condition if service depends on volume from another service", func(t *testing.T) {
|
|
||||||
project := composetypes.Project{
|
|
||||||
Name: "myProject",
|
|
||||||
Services: []composetypes.ServiceConfig{
|
|
||||||
{
|
|
||||||
Name: "aService",
|
|
||||||
VolumesFrom: []string{"anotherService"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "anotherService",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := prepareVolumes(&project)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionStarted)
|
|
||||||
})
|
|
||||||
t.Run("doesn't overwrite existing dependency condition", func(t *testing.T) {
|
|
||||||
project := composetypes.Project{
|
|
||||||
Name: "myProject",
|
|
||||||
Services: []composetypes.ServiceConfig{
|
|
||||||
{
|
|
||||||
Name: "aService",
|
|
||||||
VolumesFrom: []string{"anotherService"},
|
|
||||||
DependsOn: map[string]composetypes.ServiceDependency{
|
|
||||||
"anotherService": {Condition: composetypes.ServiceConditionHealthy, Required: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "anotherService",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := prepareVolumes(&project)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionHealthy)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildContainerMountOptions(t *testing.T) {
|
func TestBuildContainerMountOptions(t *testing.T) {
|
||||||
project := composetypes.Project{
|
project := composetypes.Project{
|
||||||
Name: "myProject",
|
Name: "myProject",
|
||||||
|
@ -58,9 +58,6 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
|
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
|
||||||
if err := prepareVolumes(project); err != nil { // all dependencies already checked, but might miss service img
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
service, err := project.GetService(opts.Service)
|
service, err := project.GetService(opts.Service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
10
pkg/e2e/fixtures/no-deps/volume-from.yaml
Normal file
10
pkg/e2e/fixtures/no-deps/volume-from.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: nginx:alpine
|
||||||
|
volumes_from:
|
||||||
|
- db
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: nginx:alpine
|
||||||
|
volumes:
|
||||||
|
- /var/data
|
44
pkg/e2e/noDeps_test.go
Normal file
44
pkg/e2e/noDeps_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/icmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoDepsVolumeFrom(t *testing.T) {
|
||||||
|
c := NewParallelCLI(t)
|
||||||
|
const projectName = "e2e-no-deps-volume-from"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||||
|
})
|
||||||
|
|
||||||
|
c.RunDockerComposeCmd(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "-d")
|
||||||
|
|
||||||
|
c.RunDockerComposeCmd(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "--no-deps", "-d", "app")
|
||||||
|
|
||||||
|
c.RunDockerCmd(t, "rm", "-f", fmt.Sprintf("%s-db-1", projectName))
|
||||||
|
|
||||||
|
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "--no-deps", "-d", "app")
|
||||||
|
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "cannot share volume with service db: container missing"})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user