From d9fe745cc0e5acff1234de2caec550112056846e Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 10 Feb 2021 14:20:32 +0100 Subject: [PATCH] avoid use of channels in API for gRPC compatibility Signed-off-by: Nicolas De Loof --- api/compose/api.go | 5 +++- cli/cmd/compose/start.go | 4 ++- cli/cmd/compose/up.go | 11 ++++++-- cli/cmd/exit.go | 28 +++++++++++++++++++ cli/main.go | 9 ++++++ go.sum | 1 + local/compose/attach.go | 4 +-- local/compose/logs.go | 8 +++--- local/compose/start.go | 4 +-- local/e2e/compose/cascade_stop_test.go | 23 +++++++++++---- .../fixtures/cascade-stop-test/compose.yaml | 4 +-- 11 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 cli/cmd/exit.go diff --git a/api/compose/api.go b/api/compose/api.go index e19da94fa..3d3f063d1 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -66,7 +66,7 @@ type CreateOptions struct { // StartOptions group options of the Start API type StartOptions struct { // Attach will attach to service containers and pipe stdout/stderr to channel - Attach chan ContainerEvent + Attach ContainerEventListener } // UpOptions group options of the Up API @@ -186,6 +186,9 @@ type LogConsumer interface { Status(service, container, msg string) } +// ContainerEventListener is a callback to process ContainerEvent from services +type ContainerEventListener func(event ContainerEvent) + // ContainerEvent notify an event has been collected on Source container implementing Service type ContainerEvent struct { Type int diff --git a/cli/cmd/compose/start.go b/cli/cmd/compose/start.go index 97792146e..1b0abb604 100644 --- a/cli/cmd/compose/start.go +++ b/cli/cmd/compose/start.go @@ -70,7 +70,9 @@ func runStart(ctx context.Context, opts startOptions, services []string) error { queue: queue, } err = c.ComposeService().Start(ctx, project, compose.StartOptions{ - Attach: queue, + Attach: func(event compose.ContainerEvent) { + queue <- event + }, }) if err != nil { return err diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go index 6585c8e91..537900e17 100644 --- a/cli/cmd/compose/up.go +++ b/cli/cmd/compose/up.go @@ -28,6 +28,7 @@ import ( "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/context/store" "github.com/docker/compose-cli/api/progress" + "github.com/docker/compose-cli/cli/cmd" "github.com/docker/compose-cli/cli/formatter" "github.com/compose-spec/compose-go/types" @@ -178,14 +179,18 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro }() err = c.ComposeService().Start(ctx, project, compose.StartOptions{ - Attach: queue, + Attach: func(event compose.ContainerEvent) { + queue <- event + }, }) if err != nil { return err } - _, err = printer.run(ctx, opts.cascadeStop, opts.exitCodeFrom, stopFunc) - // FIXME os.Exit + exitCode, err := printer.run(ctx, opts.cascadeStop, opts.exitCodeFrom, stopFunc) + if exitCode != 0 { + return cmd.ExitCodeError{ExitCode: exitCode} + } return err } diff --git a/cli/cmd/exit.go b/cli/cmd/exit.go new file mode 100644 index 000000000..fa45ad889 --- /dev/null +++ b/cli/cmd/exit.go @@ -0,0 +1,28 @@ +/* + Copyright 2020 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 cmd + +import "strconv" + +// ExitCodeError reports an exit code set by command. +type ExitCodeError struct { + ExitCode int +} + +func (e ExitCodeError) Error() string { + return strconv.Itoa(e.ExitCode) +} diff --git a/cli/main.go b/cli/main.go index d05720ca3..601657de0 100644 --- a/cli/main.go +++ b/cli/main.go @@ -170,6 +170,10 @@ func main() { fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", opts.LogLevel) os.Exit(1) } + logrus.SetFormatter(&logrus.TextFormatter{ + DisableTimestamp: true, + DisableLevelTruncation: true, + }) logrus.SetLevel(level) if opts.Debug { logrus.SetLevel(logrus.DebugLevel) @@ -241,6 +245,11 @@ $ docker context create %s `, cc.Type(), store.EcsContextType), ctype) } func exit(ctx string, err error, ctype string) { + if exit, ok := err.(cmd.ExitCodeError); ok { + metrics.Track(ctype, os.Args[1:], metrics.SuccessStatus) + os.Exit(exit.ExitCode) + } + metrics.Track(ctype, os.Args[1:], metrics.FailureStatus) if errors.Is(err, errdefs.ErrLoginRequired) { diff --git a/go.sum b/go.sum index 2480a1a71..4a2b0121f 100644 --- a/go.sum +++ b/go.sum @@ -485,6 +485,7 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= diff --git a/local/compose/attach.go b/local/compose/attach.go index 164e5b1be..d72e52e95 100644 --- a/local/compose/attach.go +++ b/local/compose/attach.go @@ -30,7 +30,7 @@ import ( "github.com/docker/docker/pkg/stdcopy" ) -func (s *composeService) attach(ctx context.Context, project *types.Project, consumer chan compose.ContainerEvent) (Containers, error) { +func (s *composeService) attach(ctx context.Context, project *types.Project, consumer compose.ContainerEventListener) (Containers, error) { containers, err := s.getContainers(ctx, project) if err != nil { return nil, err @@ -51,7 +51,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, con return containers, nil } -func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer chan compose.ContainerEvent, project *types.Project) error { +func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer compose.ContainerEventListener, project *types.Project) error { serviceName := container.Labels[serviceLabel] w := getWriter(serviceName, getContainerNameWithoutProject(container), consumer) diff --git a/local/compose/logs.go b/local/compose/logs.go index 2d47c9987..6835d9681 100644 --- a/local/compose/logs.go +++ b/local/compose/logs.go @@ -90,11 +90,11 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer type splitBuffer struct { service string container string - consumer chan compose.ContainerEvent + consumer compose.ContainerEventListener } // getWriter creates a io.Writer that will actually split by line and format by LogConsumer -func getWriter(service, container string, events chan compose.ContainerEvent) io.Writer { +func getWriter(service, container string, events compose.ContainerEventListener) io.Writer { return splitBuffer{ service: service, container: container, @@ -106,12 +106,12 @@ func (s splitBuffer) Write(b []byte) (n int, err error) { split := bytes.Split(b, []byte{'\n'}) for _, line := range split { if len(line) != 0 { - s.consumer <- compose.ContainerEvent{ + s.consumer(compose.ContainerEvent{ Type: compose.ContainerEventLog, Service: s.service, Source: s.container, Line: string(line), - } + }) } } return len(b), nil diff --git a/local/compose/start.go b/local/compose/start.go index 83ecfeabd..c1aa7cc9c 100644 --- a/local/compose/start.go +++ b/local/compose/start.go @@ -53,12 +53,12 @@ func (s *composeService) Start(ctx context.Context, project *types.Project, opti statusC, errC := s.apiClient.ContainerWait(context.Background(), c.ID, container.WaitConditionNotRunning) select { case status := <-statusC: - options.Attach <- compose.ContainerEvent{ + options.Attach(compose.ContainerEvent{ Type: compose.ContainerEventExit, Source: getCanonicalContainerName(c), Service: c.Labels[serviceLabel], ExitCode: int(status.StatusCode), - } + }) case err := <-errC: logrus.Warnf("Unexpected API error for %s : %s\n", getCanonicalContainerName(c), err.Error()) } diff --git a/local/e2e/compose/cascade_stop_test.go b/local/e2e/compose/cascade_stop_test.go index 1cb060784..34c2e09a7 100644 --- a/local/e2e/compose/cascade_stop_test.go +++ b/local/e2e/compose/cascade_stop_test.go @@ -29,10 +29,21 @@ func TestCascadeStop(t *testing.T) { const projectName = "compose-e2e-logs" - res := c.RunDockerCmd("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit") - res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`}) - res.Assert(t, icmd.Expected{Out: `/does_not_exist: No such file or directory`}) - res.Assert(t, icmd.Expected{Out: `should_fail_1 exited with code 1`}) - res.Assert(t, icmd.Expected{Out: `Aborting on container exit...`}) - // FIXME res.Assert(t, icmd.Expected{ExitCode: 1}) + t.Run("abort-on-container-exit", func(t *testing.T) { + res := c.RunDockerCmd("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit") + res.Assert(t, icmd.Expected{Out: `/does_not_exist: No such file or directory`}) + res.Assert(t, icmd.Expected{Out: `should_fail_1 exited with code 1`}) + res.Assert(t, icmd.Expected{Out: `Aborting on container exit...`}) + res.Assert(t, icmd.Expected{Out: `ERROR 1`}) + res.Assert(t, icmd.Expected{ExitCode: 1}) + }) + + t.Run("exit-code-from", func(t *testing.T) { + res := c.RunDockerCmd("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=sleep") + res.Assert(t, icmd.Expected{Out: `/does_not_exist: No such file or directory`}) + res.Assert(t, icmd.Expected{Out: `should_fail_1 exited with code 1`}) + res.Assert(t, icmd.Expected{Out: `Aborting on container exit...`}) + res.Assert(t, icmd.Expected{Out: `ERROR 143`}) + res.Assert(t, icmd.Expected{ExitCode: 143}) + }) } diff --git a/local/e2e/compose/fixtures/cascade-stop-test/compose.yaml b/local/e2e/compose/fixtures/cascade-stop-test/compose.yaml index e30fd3a5d..b6844afaa 100644 --- a/local/e2e/compose/fixtures/cascade-stop-test/compose.yaml +++ b/local/e2e/compose/fixtures/cascade-stop-test/compose.yaml @@ -2,6 +2,6 @@ services: should_fail: image: busybox:1.27.2 command: ls /does_not_exist - ping: + sleep: # will be killed image: busybox:1.27.2 - command: ping localhost + command: sleep 10