diff --git a/pkg/compose/down.go b/pkg/compose/down.go index bd716978a..03f456a6d 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -306,6 +306,10 @@ func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, s for _, hook := range service.PreStop { err := s.runHook(ctx, ctr, *service, hook, nil) if err != nil { + // Ignore errors indicating that some containers were already stopped or removed. + if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) { + return nil + } return err } } diff --git a/pkg/e2e/fixtures/hooks/compose.yaml b/pkg/e2e/fixtures/hooks/compose.yaml new file mode 100644 index 000000000..b45a65df0 --- /dev/null +++ b/pkg/e2e/fixtures/hooks/compose.yaml @@ -0,0 +1,14 @@ +services: + sample: + image: nginx + volumes: + - data:/data + pre_stop: + - command: sh -c 'echo "In the pre-stop" >> /data/log.txt' + test: + image: nginx + post_start: + - command: sh -c 'echo env' +volumes: + data: + name: sample-data \ No newline at end of file diff --git a/pkg/e2e/fixtures/hooks/poststart/compose-error.yaml b/pkg/e2e/fixtures/hooks/poststart/compose-error.yaml new file mode 100644 index 000000000..eaee936b8 --- /dev/null +++ b/pkg/e2e/fixtures/hooks/poststart/compose-error.yaml @@ -0,0 +1,6 @@ +services: + + test: + image: alpine + post_start: + - command: sh -c 'echo env' diff --git a/pkg/e2e/fixtures/hooks/poststart/compose-success.yaml b/pkg/e2e/fixtures/hooks/poststart/compose-success.yaml new file mode 100644 index 000000000..727794588 --- /dev/null +++ b/pkg/e2e/fixtures/hooks/poststart/compose-success.yaml @@ -0,0 +1,5 @@ +services: + test: + image: nginx + post_start: + - command: sh -c 'echo env' diff --git a/pkg/e2e/fixtures/hooks/prestop/compose-error.yaml b/pkg/e2e/fixtures/hooks/prestop/compose-error.yaml new file mode 100644 index 000000000..1beb6c7e5 --- /dev/null +++ b/pkg/e2e/fixtures/hooks/prestop/compose-error.yaml @@ -0,0 +1,10 @@ +services: + sample: + image: nginx + volumes: + - data:/data + pre_stop: + - command: sh -c 'command in error' +volumes: + data: + name: sample-data \ No newline at end of file diff --git a/pkg/e2e/fixtures/hooks/prestop/compose-success.yaml b/pkg/e2e/fixtures/hooks/prestop/compose-success.yaml new file mode 100644 index 000000000..0d8058211 --- /dev/null +++ b/pkg/e2e/fixtures/hooks/prestop/compose-success.yaml @@ -0,0 +1,10 @@ +services: + sample: + image: nginx + volumes: + - data:/data + pre_stop: + - command: sh -c 'echo "In the pre-stop" >> /data/log.txt' +volumes: + data: + name: sample-data \ No newline at end of file diff --git a/pkg/e2e/hooks_test.go b/pkg/e2e/hooks_test.go new file mode 100644 index 000000000..df60a207b --- /dev/null +++ b/pkg/e2e/hooks_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2023 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 ( + "strings" + "testing" + + "gotest.tools/v3/assert" + "gotest.tools/v3/icmd" +) + +func TestPostStartHookInError(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-post-start-failure" + + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + }) + + res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/hooks/poststart/compose-error.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 1}) + assert.Assert(t, strings.Contains(res.Combined(), "Error response from daemon: container"), res.Combined()) + assert.Assert(t, strings.Contains(res.Combined(), "is not running"), res.Combined()) +} + +func TestPostStartHookSuccess(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-post-start-success" + + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/poststart/compose-success.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 0}) +} + +func TestPreStopHookSuccess(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-pre-stop-success" + + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/prestop/compose-success.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/prestop/compose-success.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 0}) + + res = c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/prestop/compose-success.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + res.Assert(t, icmd.Expected{ExitCode: 0}) +} + +func TestPreStopHookInError(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-pre-stop-failure" + + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/prestop/compose-success.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + }) + + res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/hooks/prestop/compose-error.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 0}) + + res = c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/hooks/prestop/compose-error.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + res.Assert(t, icmd.Expected{ExitCode: 1}) + assert.Assert(t, strings.Contains(res.Combined(), "sample hook exited with status 127")) +} + +func TestPreStopHookSuccessWithPreviousStop(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-pre-stop-success-with-previous-stop" + + t.Cleanup(func() { + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/compose.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + res.Assert(t, icmd.Expected{ExitCode: 0}) + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/compose.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 0}) + + res = c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/compose.yaml", "--project-name", projectName, "stop", "sample") + res.Assert(t, icmd.Expected{ExitCode: 0}) +} + +func TestPostStartAndPreStopHook(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "hooks-post-start-and-pre-stop" + + t.Cleanup(func() { + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/compose.yaml", "--project-name", projectName, "down", "-v", "--remove-orphans", "-t", "0") + res.Assert(t, icmd.Expected{ExitCode: 0}) + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/hooks/compose.yaml", "--project-name", projectName, "up", "-d") + res.Assert(t, icmd.Expected{ExitCode: 0}) +}