From 01d91c490ca4e93d092d442cbbc62b5f46416ace Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 25 May 2023 17:02:08 +0200 Subject: [PATCH 1/2] detect new container from project have started when running `compose logs` with no explicit services Signed-off-by: Nicolas De Loof --- pkg/compose/events.go | 5 ++--- pkg/compose/logs.go | 7 +++---- pkg/compose/start.go | 25 ++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pkg/compose/events.go b/pkg/compose/events.go index 172e3eec2..e664133be 100644 --- a/pkg/compose/events.go +++ b/pkg/compose/events.go @@ -25,7 +25,6 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose/v2/pkg/utils" ) @@ -67,8 +66,8 @@ func (s *composeService) Events(ctx context.Context, projectName string, options err := options.Consumer(api.Event{ Timestamp: timestamp, Service: service, - Container: event.ID, - Status: event.Status, + Container: event.Actor.ID, + Status: event.Action, Attributes: attributes, }) if err != nil { diff --git a/pkg/compose/logs.go b/pkg/compose/logs.go index 6a67d0143..2d9d716d0 100644 --- a/pkg/compose/logs.go +++ b/pkg/compose/logs.go @@ -51,13 +51,12 @@ func (s *composeService) Logs( if err != nil { return err } - } - - if len(options.Services) == 0 { + } else if len(options.Services) == 0 { + // we run with an explicit compose.yaml, so only consider services defined in this file options.Services = project.ServiceNames() + containers = containers.filter(isService(options.Services...)) } - containers = containers.filter(isService(options.Services...)) eg, ctx := errgroup.WithContext(ctx) for _, c := range containers { c := c diff --git a/pkg/compose/start.go b/pkg/compose/start.go index bd2ebcacd..66520cd84 100644 --- a/pkg/compose/start.go +++ b/pkg/compose/start.go @@ -155,13 +155,31 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo required = services } + // predicate to tell if a container we receive event for should be considered or ignored + ofInterest := func(c moby.Container) bool { + if len(services) > 0 { + // we only watch some services + return utils.Contains(services, c.Labels[api.ServiceLabel]) + } + return true + } + + // predicate to tell if a container we receive event for should be watched until termination + isRequired := func(c moby.Container) bool { + if len(services) > 0 && len(required) > 0 { + // we only watch some services + return utils.Contains(required, c.Labels[api.ServiceLabel]) + } + return true + } + var ( expected []string watched = map[string]int{} replaced []string ) for _, c := range containers { - if utils.Contains(required, c.Labels[api.ServiceLabel]) { + if isRequired(c) { expected = append(expected, c.ID) } watched[c.ID] = 0 @@ -265,6 +283,11 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo if utils.Contains(expected, id) { expected = append(expected, container.ID) } + } else if ofInterest(container) { + watched[container.ID] = 1 + if isRequired(container) { + expected = append(expected, container.ID) + } } } if len(expected) == 0 { From 0d6b99e6f9c890a0999a4bdb236f29db6404a320 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 26 May 2023 10:42:08 +0200 Subject: [PATCH 2/2] e2e test to cover logs -f managing service being added/scaled Signed-off-by: Nicolas De Loof --- pkg/compose/logs.go | 10 ++---- pkg/e2e/fixtures/logs-test/compose.yaml | 2 +- pkg/e2e/logs_test.go | 42 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pkg/compose/logs.go b/pkg/compose/logs.go index 2d9d716d0..f5a20b16a 100644 --- a/pkg/compose/logs.go +++ b/pkg/compose/logs.go @@ -45,15 +45,9 @@ func (s *composeService) Logs( return err } - project := options.Project - if project == nil { - project, err = s.getProjectWithResources(ctx, containers, projectName) - if err != nil { - return err - } - } else if len(options.Services) == 0 { + if options.Project != nil && len(options.Services) == 0 { // we run with an explicit compose.yaml, so only consider services defined in this file - options.Services = project.ServiceNames() + options.Services = options.Project.ServiceNames() containers = containers.filter(isService(options.Services...)) } diff --git a/pkg/e2e/fixtures/logs-test/compose.yaml b/pkg/e2e/fixtures/logs-test/compose.yaml index 2b2c4c3bb..f7440a238 100644 --- a/pkg/e2e/fixtures/logs-test/compose.yaml +++ b/pkg/e2e/fixtures/logs-test/compose.yaml @@ -1,7 +1,7 @@ services: ping: image: alpine - command: ping localhost -c 1 + command: ping localhost -c ${REPEAT:-1} hello: image: alpine command: echo hello diff --git a/pkg/e2e/logs_test.go b/pkg/e2e/logs_test.go index 42d47d050..e4e6f41d4 100644 --- a/pkg/e2e/logs_test.go +++ b/pkg/e2e/logs_test.go @@ -17,10 +17,13 @@ package e2e import ( + "fmt" "strings" "testing" + "time" "gotest.tools/v3/assert" + "gotest.tools/v3/poll" "gotest.tools/v3/icmd" ) @@ -56,3 +59,42 @@ func TestLocalComposeLogs(t *testing.T) { _ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") }) } + +func TestLocalComposeLogsFollow(t *testing.T) { + c := NewCLI(t, WithEnv("REPEAT=20")) + const projectName = "compose-e2e-logs" + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "--project-name", projectName, "down") + }) + + c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d", "ping") + + cmd := c.NewDockerComposeCmd(t, "--project-name", projectName, "logs", "-f") + res := icmd.StartCmd(cmd) + t.Cleanup(func() { + _ = res.Cmd.Process.Kill() + }) + + expected := fmt.Sprintf("%s-ping-1 ", projectName) + poll.WaitOn(t, expectOutput(res, expected), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(1*time.Second)) + + c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d") + + expected = fmt.Sprintf("%s-hello-1 ", projectName) + poll.WaitOn(t, expectOutput(res, expected), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(1*time.Second)) + + c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d", "--scale", "ping=2", "ping") + + expected = fmt.Sprintf("%s-ping-2 ", projectName) + poll.WaitOn(t, expectOutput(res, expected), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(20*time.Second)) +} + +func expectOutput(res *icmd.Result, expected string) func(t poll.LogT) poll.Result { + return func(t poll.LogT) poll.Result { + if strings.Contains(res.Stdout(), expected) { + return poll.Success() + } + return poll.Continue("condition not met") + + } +}