Skip to content

Commit f6e31db

Browse files
committed
don't rely on depends_on to resolve volume_from, better use observed state
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent e19232e commit f6e31db

File tree

6 files changed

+80
-115
lines changed

6 files changed

+80
-115
lines changed

pkg/compose/convergence.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
178178

179179
eg, _ := errgroup.WithContext(ctx)
180180

181+
err = c.resolveVolumeFrom(&service)
182+
if err != nil {
183+
return err
184+
}
185+
181186
sort.Slice(containers, func(i, j int) bool {
182187
return containers[i].Created < containers[j].Created
183188
})
@@ -258,6 +263,26 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
258263
return err
259264
}
260265

266+
func (c *convergence) resolveVolumeFrom(service *types.ServiceConfig) error {
267+
for i, vol := range service.VolumesFrom {
268+
spec := strings.Split(vol, ":")
269+
if len(spec) == 0 {
270+
continue
271+
}
272+
if spec[0] == "container" {
273+
service.VolumesFrom[i] = spec[1]
274+
continue
275+
}
276+
name := spec[0]
277+
dependencies := c.getObservedState(name)
278+
if len(dependencies) == 0 {
279+
return fmt.Errorf("cannot share volume with service %s: container missing", name)
280+
}
281+
service.VolumesFrom[i] = dependencies.sorted()[0].ID
282+
}
283+
return nil
284+
}
285+
261286
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
262287
if policy == api.RecreateNever {
263288
return false, nil

pkg/compose/create.go

Lines changed: 1 addition & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
8686

8787
prepareNetworks(project)
8888

89-
err = prepareVolumes(project)
90-
if err != nil {
91-
return err
92-
}
93-
9489
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
9590
return err
9691
}
@@ -123,31 +118,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
123118
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
124119
}
125120

126-
func prepareVolumes(p *types.Project) error {
127-
for i := range p.Services {
128-
volumesFrom, dependServices, err := getVolumesFrom(p, p.Services[i].VolumesFrom)
129-
if err != nil {
130-
return err
131-
}
132-
p.Services[i].VolumesFrom = volumesFrom
133-
if len(dependServices) > 0 {
134-
if p.Services[i].DependsOn == nil {
135-
p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
136-
}
137-
for _, service := range p.Services {
138-
if utils.StringContains(dependServices, service.Name) &&
139-
p.Services[i].DependsOn[service.Name].Condition == "" {
140-
p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
141-
Condition: types.ServiceConditionStarted,
142-
Required: true,
143-
}
144-
}
145-
}
146-
}
147-
}
148-
return nil
149-
}
150-
151121
func prepareNetworks(project *types.Project) {
152122
for k, network := range project.Networks {
153123
network.Labels = network.Labels.Add(api.NetworkLabel, k)
@@ -249,13 +219,6 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
249219
if err != nil {
250220
return createConfigs{}, err
251221
}
252-
var volumesFrom []string
253-
for _, v := range service.VolumesFrom {
254-
if !strings.HasPrefix(v, "container:") {
255-
return createConfigs{}, fmt.Errorf("invalid volume_from: %s", v)
256-
}
257-
volumesFrom = append(volumesFrom, v[len("container:"):])
258-
}
259222

260223
// NETWORKING
261224
links, err := s.getLinks(ctx, p.Name, service, number)
@@ -296,7 +259,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
296259
PortBindings: portBindings,
297260
Resources: resources,
298261
VolumeDriver: service.VolumeDriver,
299-
VolumesFrom: volumesFrom,
262+
VolumesFrom: service.VolumesFrom,
300263
DNS: service.DNS,
301264
DNSSearch: service.DNSSearch,
302265
DNSOptions: service.DNSOpts,
@@ -676,40 +639,6 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
676639
return bindings
677640
}
678641

679-
func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
680-
var volumes = []string{}
681-
var services = []string{}
682-
// parse volumes_from
683-
if len(volumesFrom) == 0 {
684-
return volumes, services, nil
685-
}
686-
for _, vol := range volumesFrom {
687-
spec := strings.Split(vol, ":")
688-
if len(spec) == 0 {
689-
continue
690-
}
691-
if spec[0] == "container" {
692-
volumes = append(volumes, vol)
693-
continue
694-
}
695-
serviceName := spec[0]
696-
services = append(services, serviceName)
697-
service, err := project.GetService(serviceName)
698-
if err != nil {
699-
return nil, nil, err
700-
}
701-
702-
firstContainer := getContainerName(project.Name, service, 1)
703-
v := fmt.Sprintf("container:%s", firstContainer)
704-
if len(spec) > 2 {
705-
v = fmt.Sprintf("container:%s:%s", firstContainer, strings.Join(spec[1:], ":"))
706-
}
707-
volumes = append(volumes, v)
708-
}
709-
return volumes, services, nil
710-
711-
}
712-
713642
func getDependentServiceFromMode(mode string) string {
714643
if strings.HasPrefix(
715644
mode,

pkg/compose/create_test.go

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -98,46 +98,6 @@ func TestPrepareNetworkLabels(t *testing.T) {
9898
}))
9999
}
100100

101-
func TestPrepareVolumes(t *testing.T) {
102-
t.Run("adds dependency condition if service depends on volume from another service", func(t *testing.T) {
103-
project := composetypes.Project{
104-
Name: "myProject",
105-
Services: []composetypes.ServiceConfig{
106-
{
107-
Name: "aService",
108-
VolumesFrom: []string{"anotherService"},
109-
},
110-
{
111-
Name: "anotherService",
112-
},
113-
},
114-
}
115-
err := prepareVolumes(&project)
116-
assert.NilError(t, err)
117-
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionStarted)
118-
})
119-
t.Run("doesn't overwrite existing dependency condition", func(t *testing.T) {
120-
project := composetypes.Project{
121-
Name: "myProject",
122-
Services: []composetypes.ServiceConfig{
123-
{
124-
Name: "aService",
125-
VolumesFrom: []string{"anotherService"},
126-
DependsOn: map[string]composetypes.ServiceDependency{
127-
"anotherService": {Condition: composetypes.ServiceConditionHealthy, Required: true},
128-
},
129-
},
130-
{
131-
Name: "anotherService",
132-
},
133-
},
134-
}
135-
err := prepareVolumes(&project)
136-
assert.NilError(t, err)
137-
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionHealthy)
138-
})
139-
}
140-
141101
func TestBuildContainerMountOptions(t *testing.T) {
142102
project := composetypes.Project{
143103
Name: "myProject",

pkg/compose/run.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
5858
}
5959

6060
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
61-
if err := prepareVolumes(project); err != nil { // all dependencies already checked, but might miss service img
62-
return "", err
63-
}
6461
service, err := project.GetService(opts.Service)
6562
if err != nil {
6663
return "", err
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
app:
3+
image: nginx:alpine
4+
volumes_from:
5+
- db
6+
7+
db:
8+
image: nginx:alpine
9+
volumes:
10+
- /var/data

pkg/e2e/noDeps_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
/*
5+
Copyright 2022 Docker Compose CLI authors
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package e2e
21+
22+
import (
23+
"fmt"
24+
"testing"
25+
26+
"gotest.tools/v3/icmd"
27+
)
28+
29+
func TestNoDepsVolumeFrom(t *testing.T) {
30+
c := NewParallelCLI(t)
31+
const projectName = "e2e-no-deps-volume-from"
32+
t.Cleanup(func() {
33+
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
34+
})
35+
36+
c.RunDockerComposeCmd(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "-d")
37+
38+
c.RunDockerComposeCmd(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "--no-deps", "-d", "app")
39+
40+
c.RunDockerCmd(t, "rm", "-f", fmt.Sprintf("%s-db-1", projectName))
41+
42+
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/no-deps/volume-from.yaml", "--project-name", projectName, "up", "--no-deps", "-d", "app")
43+
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "cannot share volume with service db: container missing"})
44+
}

0 commit comments

Comments
 (0)