Skip to content

Commit 2db28f3

Browse files
authored
Merge pull request #4550 from ChengyuZhu6/compose
compose: align convergence with Docker Compose
2 parents 4d28f8b + a3c783b commit 2db28f3

File tree

6 files changed

+101
-2
lines changed

6 files changed

+101
-2
lines changed

cmd/nerdctl/compose/compose_create_linux_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/containerd/nerdctl/mod/tigron/test"
2828
"github.com/containerd/nerdctl/mod/tigron/tig"
2929

30+
"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser"
3031
"github.com/containerd/nerdctl/v2/pkg/testutil"
3132
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
3233
)
@@ -204,3 +205,24 @@ services:
204205
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "svc0").AssertOutContains(imageSvc0)
205206
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created")
206207
}
208+
209+
func TestComposeCreateWritesConfigHashLabel(t *testing.T) {
210+
var dockerComposeYAML = fmt.Sprintf(`
211+
services:
212+
svc0:
213+
image: %s
214+
`, testutil.CommonImage)
215+
216+
base := testutil.NewBase(t)
217+
comp := testutil.NewComposeDir(t, dockerComposeYAML)
218+
defer comp.CleanUp()
219+
projectName := comp.ProjectName()
220+
t.Logf("projectName=%q", projectName)
221+
222+
base.ComposeCmd("-f", comp.YAMLFullPath(), "create").AssertOK()
223+
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
224+
225+
container := serviceparser.DefaultContainerName(projectName, "svc0", "1")
226+
base.Cmd("inspect", "--format", "{{json .Config.Labels}}", container).
227+
AssertOutContains("com.docker.compose.config-hash")
228+
}

cmd/nerdctl/compose/compose_up_linux_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,46 @@ services:
377377
base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK()
378378
}
379379

380+
func TestComposeUpNoRecreateDependencies(t *testing.T) {
381+
base := testutil.NewBase(t)
382+
383+
var dockerComposeYAML = fmt.Sprintf(`
384+
services:
385+
foo:
386+
image: %s
387+
command: "sleep infinity"
388+
bar:
389+
image: %s
390+
command: "sleep infinity"
391+
depends_on:
392+
- foo
393+
`, testutil.CommonImage, testutil.CommonImage)
394+
395+
comp := testutil.NewComposeDir(t, dockerComposeYAML)
396+
defer comp.CleanUp()
397+
projectName := comp.ProjectName()
398+
t.Logf("projectName=%q", projectName)
399+
400+
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "foo").AssertOK()
401+
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
402+
403+
fooName := serviceparser.DefaultContainerName(projectName, "foo", "1")
404+
id1Cmd := base.Cmd("inspect", fooName, "--format", "{{.Id}}")
405+
id1Res := id1Cmd.Run()
406+
out1 := strings.TrimSpace(id1Res.Stdout())
407+
assert.Assert(id1Cmd.Base.T, id1Res.ExitCode == 0, id1Res.Stdout()+id1Res.Stderr())
408+
409+
// Bring up dependent service; ensure foo is not recreated (ID unchanged)
410+
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "bar").AssertOK()
411+
412+
id2Cmd := base.Cmd("inspect", fooName, "--format", "{{.Id}}")
413+
id2Res := id2Cmd.Run()
414+
out2 := strings.TrimSpace(id2Res.Stdout())
415+
assert.Assert(id2Cmd.Base.T, id2Res.ExitCode == 0, id2Res.Stdout()+id2Res.Stderr())
416+
417+
assert.Equal(base.T, out1, out2)
418+
}
419+
380420
func TestComposeUpWithExternalNetwork(t *testing.T) {
381421
testCase := nerdtest.Setup()
382422

pkg/composer/create.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,15 @@ func (c *Composer) createServiceContainer(ctx context.Context, service *servicep
188188
cidFilename := filepath.Join(tempDir, "cid")
189189

190190
//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels
191+
currentHash, err := ServiceHash(*service.Unparsed)
192+
if err != nil {
193+
return "", fmt.Errorf("failed computing service hash for %s: %w", container.Name, err)
194+
}
191195
container.RunArgs = append([]string{
192196
"--cidfile=" + cidFilename,
193197
fmt.Sprintf("-l=%s=%s", labels.ComposeProject, c.project.Name),
194198
fmt.Sprintf("-l=%s=%s", labels.ComposeService, service.Unparsed.Name),
199+
fmt.Sprintf("-l=%s=%s", labels.ComposeConfigHash, currentHash),
195200
}, container.RunArgs...)
196201

197202
cmd := c.createNerdctlCmd(ctx, append([]string{"create"}, container.RunArgs...)...)

pkg/composer/up.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) erro
8585

8686
var parsedServices []*serviceparser.Service
8787
// use WithServices to sort the services in dependency order
88-
if err := c.project.ForEachService(services, func(name string, svc *types.ServiceConfig) error {
88+
forEachFn := func(name string, svc *types.ServiceConfig) error {
8989
if replicas, ok := uo.Scale[svc.Name]; ok {
9090
if svc.Deploy == nil {
9191
svc.Deploy = &types.DeployConfig{}
@@ -98,7 +98,9 @@ func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) erro
9898
}
9999
parsedServices = append(parsedServices, ps)
100100
return nil
101-
}); err != nil {
101+
}
102+
err := c.project.ForEachService(services, forEachFn)
103+
if err != nil {
102104
return err
103105
}
104106

pkg/composer/up_service.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,28 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
155155

156156
// delete container if it already exists
157157
if existingCid != "" {
158+
// Default behavior for RecreateDiverged: compare stored hash with current service hash
159+
if recreate == RecreateDiverged {
160+
currentHash, err := ServiceHash(*service.Unparsed)
161+
if err != nil {
162+
return "", fmt.Errorf("failed computing service hash for %s: %w", container.Name, err)
163+
}
164+
con, err := c.client.LoadContainer(ctx, existingCid)
165+
if err != nil {
166+
return "", fmt.Errorf("failed to load container %s: %w", existingCid, err)
167+
}
168+
lbls, err := con.Labels(ctx)
169+
if err != nil {
170+
return "", fmt.Errorf("failed to read labels for %s: %w", existingCid, err)
171+
}
172+
if lbls[labels.ComposeConfigHash] == currentHash {
173+
cmd := c.createNerdctlCmd(ctx, append([]string{"start"}, existingCid)...)
174+
if err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {
175+
return "", fmt.Errorf("error while starting existing container %s: %w", container.Name, err)
176+
}
177+
return existingCid, nil
178+
}
179+
}
158180
log.G(ctx).Debugf("Container %q already exists, deleting", container.Name)
159181
delCmd := c.createNerdctlCmd(ctx, "rm", "-f", container.Name)
160182
if err = delCmd.Run(); err != nil {
@@ -184,10 +206,15 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
184206
}
185207

186208
//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels
209+
currentHash, err := ServiceHash(*service.Unparsed)
210+
if err != nil {
211+
return "", fmt.Errorf("failed computing service hash for %s: %w", container.Name, err)
212+
}
187213
container.RunArgs = append([]string{
188214
"--cidfile=" + cidFilename,
189215
fmt.Sprintf("-l=%s=%s", labels.ComposeProject, c.project.Name),
190216
fmt.Sprintf("-l=%s=%s", labels.ComposeService, service.Unparsed.Name),
217+
fmt.Sprintf("-l=%s=%s", labels.ComposeConfigHash, currentHash),
191218
}, container.RunArgs...)
192219

193220
cmd := c.createNerdctlCmd(ctx, append([]string{"run"}, container.RunArgs...)...)

pkg/labels/labels.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const (
4141
//Compose Volume Name
4242
ComposeVolume = "com.docker.compose.volume"
4343

44+
// ComposeConfigHash stores the service configuration hash used for convergence decisions
45+
ComposeConfigHash = "com.docker.compose.config-hash"
46+
4447
// Hostname
4548
Hostname = Prefix + "hostname"
4649

0 commit comments

Comments
 (0)