Skip to content

Commit 765c071

Browse files
authored
up: do not stop dependency containers (docker#9701)
This keeps parity with v1, where only the containers explicitly passed to `up` are torn down when `Ctrl-C` is hit, so any dependencies that got launched (or orphan containers hanging around) should not be touched. Fixes docker#9696. Signed-off-by: Milas Bowman <[email protected]>
1 parent 3dfdad6 commit 765c071

File tree

6 files changed

+203
-3
lines changed

6 files changed

+203
-3
lines changed

pkg/compose/up.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
6161
go func() {
6262
<-signalChan
6363
s.Kill(ctx, project.Name, api.KillOptions{ //nolint:errcheck
64-
Services: project.ServiceNames(),
64+
Services: options.Create.Services,
6565
})
6666
}()
6767

6868
return s.Stop(ctx, project.Name, api.StopOptions{
69-
Services: project.ServiceNames(),
69+
Services: options.Create.Services,
7070
})
7171
})
7272
}

pkg/e2e/assert.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2022 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"encoding/json"
21+
"strings"
22+
"testing"
23+
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
// RequireServiceState ensures that the container is in the expected state
28+
// (running or exited).
29+
func RequireServiceState(t testing.TB, cli *CLI, service string, state string) {
30+
t.Helper()
31+
psRes := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
32+
var psOut []map[string]interface{}
33+
require.NoError(t, json.Unmarshal([]byte(psRes.Stdout()), &psOut),
34+
"Invalid `compose ps` JSON output")
35+
36+
for _, svc := range psOut {
37+
require.Equal(t, service, svc["Service"],
38+
"Found ps output for unexpected service")
39+
require.Equalf(t,
40+
strings.ToLower(state),
41+
strings.ToLower(svc["State"].(string)),
42+
"Service %q (%s) not in expected state",
43+
service, svc["Name"],
44+
)
45+
}
46+
}

pkg/e2e/buffer.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright 2022 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"bytes"
21+
"strings"
22+
"sync"
23+
"testing"
24+
"time"
25+
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
type lockedBuffer struct {
30+
mu sync.Mutex
31+
buf bytes.Buffer
32+
}
33+
34+
func (l *lockedBuffer) Read(p []byte) (n int, err error) {
35+
l.mu.Lock()
36+
defer l.mu.Unlock()
37+
return l.buf.Read(p)
38+
}
39+
40+
func (l *lockedBuffer) Write(p []byte) (n int, err error) {
41+
l.mu.Lock()
42+
defer l.mu.Unlock()
43+
return l.buf.Write(p)
44+
}
45+
46+
func (l *lockedBuffer) String() string {
47+
l.mu.Lock()
48+
defer l.mu.Unlock()
49+
return l.buf.String()
50+
}
51+
52+
func (l *lockedBuffer) RequireEventuallyContains(t testing.TB, v string) {
53+
t.Helper()
54+
var bufContents strings.Builder
55+
require.Eventuallyf(t, func() bool {
56+
l.mu.Lock()
57+
defer l.mu.Unlock()
58+
if _, err := l.buf.WriteTo(&bufContents); err != nil {
59+
require.FailNowf(t, "Failed to copy from buffer",
60+
"Error: %v", err)
61+
}
62+
return strings.Contains(bufContents.String(), v)
63+
}, 2*time.Second, 20*time.Millisecond,
64+
"Buffer did not contain %q\n============\n%s\n============",
65+
v, &bufContents)
66+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
dependency:
3+
image: alpine
4+
init: true
5+
command: /bin/sh -c 'while true; do echo "hello dependency"; sleep 1; done'
6+
7+
app:
8+
depends_on: ['dependency']
9+
image: alpine
10+
init: true
11+
command: /bin/sh -c 'while true; do echo "hello app"; sleep 1; done'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
orphan:
3+
image: alpine
4+
init: true
5+
command: /bin/sh -c 'while true; do echo "hello orphan"; sleep 1; done'

pkg/e2e/up_test.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2020 Docker Compose CLI authors
2+
Copyright 2022 Docker Compose CLI authors
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,8 +17,14 @@
1717
package e2e
1818

1919
import (
20+
"context"
21+
"os/exec"
22+
"syscall"
2023
"testing"
24+
"time"
2125

26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
2228
"gotest.tools/v3/icmd"
2329
)
2430

@@ -31,3 +37,69 @@ func TestUpServiceUnhealthy(t *testing.T) {
3137

3238
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
3339
}
40+
41+
func TestUpDependenciesNotStopped(t *testing.T) {
42+
c := NewParallelCLI(t, WithEnv(
43+
"COMPOSE_PROJECT_NAME=up-deps-stop",
44+
))
45+
46+
reset := func() {
47+
c.RunDockerComposeCmdNoCheck(t, "down", "-t=0", "--remove-orphans", "-v")
48+
}
49+
reset()
50+
t.Cleanup(reset)
51+
52+
ctx, cancel := context.WithCancel(context.Background())
53+
defer cancel()
54+
55+
t.Log("Launching orphan container (background)")
56+
c.RunDockerComposeCmd(t,
57+
"-f=./fixtures/ups-deps-stop/orphan.yaml",
58+
"up",
59+
"--wait",
60+
"--detach",
61+
"orphan",
62+
)
63+
RequireServiceState(t, c, "orphan", "running")
64+
65+
t.Log("Launching app container with implicit dependency")
66+
var upOut lockedBuffer
67+
var upCmd *exec.Cmd
68+
go func() {
69+
testCmd := c.NewDockerComposeCmd(t,
70+
"-f=./fixtures/ups-deps-stop/compose.yaml",
71+
"up",
72+
"app",
73+
)
74+
cmd := exec.CommandContext(ctx, testCmd.Command[0], testCmd.Command[1:]...)
75+
cmd.Env = testCmd.Env
76+
cmd.Stdout = &upOut
77+
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
78+
79+
assert.NoError(t, cmd.Start(), "Failed to run compose up")
80+
upCmd = cmd
81+
}()
82+
83+
t.Log("Waiting for containers to be in running state")
84+
upOut.RequireEventuallyContains(t, "hello app")
85+
RequireServiceState(t, c, "app", "running")
86+
RequireServiceState(t, c, "dependency", "running")
87+
88+
t.Log("Simulating Ctrl-C")
89+
require.NoError(t, syscall.Kill(-upCmd.Process.Pid, syscall.SIGINT),
90+
"Failed to send SIGINT to compose up process")
91+
92+
time.AfterFunc(5*time.Second, cancel)
93+
94+
t.Log("Waiting for `compose up` to exit")
95+
err := upCmd.Wait()
96+
if err != nil {
97+
exitErr := err.(*exec.ExitError)
98+
require.EqualValues(t, exitErr.ExitCode(), 130)
99+
}
100+
101+
RequireServiceState(t, c, "app", "exited")
102+
// dependency should still be running
103+
RequireServiceState(t, c, "dependency", "running")
104+
RequireServiceState(t, c, "orphan", "running")
105+
}

0 commit comments

Comments
 (0)