Merge pull request #10622 from ndeloof/logs_follow

fix `compose -p x logs -f` detect new services started after command
This commit is contained in:
Guillaume Lours 2023-06-02 09:39:20 +02:00 committed by GitHub
commit 7c3fe359b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 16 deletions

View File

@ -25,7 +25,6 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils" "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{ err := options.Consumer(api.Event{
Timestamp: timestamp, Timestamp: timestamp,
Service: service, Service: service,
Container: event.ID, Container: event.Actor.ID,
Status: event.Status, Status: event.Action,
Attributes: attributes, Attributes: attributes,
}) })
if err != nil { if err != nil {

View File

@ -45,19 +45,12 @@ func (s *composeService) Logs(
return err return err
} }
project := options.Project if options.Project != nil && len(options.Services) == 0 {
if project == nil { // we run with an explicit compose.yaml, so only consider services defined in this file
project, err = s.getProjectWithResources(ctx, containers, projectName) options.Services = options.Project.ServiceNames()
if err != nil { containers = containers.filter(isService(options.Services...))
return err
}
} }
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
}
containers = containers.filter(isService(options.Services...))
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers { for _, c := range containers {
c := c c := c

View File

@ -155,13 +155,31 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
required = services 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 ( var (
expected []string expected []string
watched = map[string]int{} watched = map[string]int{}
replaced []string replaced []string
) )
for _, c := range containers { for _, c := range containers {
if utils.Contains(required, c.Labels[api.ServiceLabel]) { if isRequired(c) {
expected = append(expected, c.ID) expected = append(expected, c.ID)
} }
watched[c.ID] = 0 watched[c.ID] = 0
@ -265,6 +283,11 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
if utils.Contains(expected, id) { if utils.Contains(expected, id) {
expected = append(expected, container.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 { if len(expected) == 0 {

View File

@ -1,7 +1,7 @@
services: services:
ping: ping:
image: alpine image: alpine
command: ping localhost -c 1 command: ping localhost -c ${REPEAT:-1}
hello: hello:
image: alpine image: alpine
command: echo hello command: echo hello

View File

@ -17,10 +17,13 @@
package e2e package e2e
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
"time"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/poll"
"gotest.tools/v3/icmd" "gotest.tools/v3/icmd"
) )
@ -56,3 +59,42 @@ func TestLocalComposeLogs(t *testing.T) {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") _ = 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")
}
}