diff --git a/pkg/compose/start.go b/pkg/compose/start.go index 1d1c264cf..b0bde1f68 100644 --- a/pkg/compose/start.go +++ b/pkg/compose/start.go @@ -28,7 +28,6 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/sync/errgroup" ) func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error { @@ -52,18 +51,6 @@ func (s *composeService) start(ctx context.Context, projectName string, options } } - // use an independent context tied to the errgroup for background attach operations - // the primary context is still used for other operations - // this means that once any attach operation fails, all other attaches are cancelled, - // but an attach failing won't interfere with the rest of the start - eg, attachCtx := errgroup.WithContext(ctx) - if listener != nil { - _, err := s.attach(attachCtx, project, listener, options.AttachTo) - if err != nil { - return err - } - } - var containers Containers containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{ Filters: filters.NewArgs( @@ -111,7 +98,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options } } - return eg.Wait() + return nil } // getDependencyCondition checks if service is depended on by other services diff --git a/pkg/compose/up.go b/pkg/compose/up.go index 124976b48..f58882fbc 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "os/signal" + "slices" "sync/atomic" "syscall" @@ -34,6 +35,7 @@ import ( "github.com/eiannone/keyboard" "github.com/hashicorp/go-multierror" "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" ) func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo @@ -205,29 +207,44 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options }) } + // use an independent context tied to the errgroup for background attach operations + // the primary context is still used for other operations + // this means that once any attach operation fails, all other attaches are cancelled, + // but an attach failing won't interfere with the rest of the start + _, attachCtx := errgroup.WithContext(ctx) + containers, err := s.attach(attachCtx, project, printer.HandleEvent, options.Start.AttachTo) + if err != nil { + return err + } + attached := make([]string, len(containers)) + for i, ctr := range containers { + attached[i] = ctr.ID + } + monitor.withListener(func(event api.ContainerEvent) { if event.Type != api.ContainerEventStarted { return } - if event.Restarting || event.Container.Labels[api.ContainerReplaceLabel] != "" { - eg.Go(func() error { - ctr, err := s.apiClient().ContainerInspect(ctx, event.ID) - if err != nil { - return err - } - - err = s.doLogContainer(ctx, options.Start.Attach, event.Source, ctr, api.LogOptions{ - Follow: true, - Since: ctr.State.StartedAt, - }) - if errdefs.IsNotImplemented(err) { - // container may be configured with logging_driver: none - // as container already started, we might miss the very first logs. But still better than none - return s.doAttachContainer(ctx, event.Service, event.ID, event.Source, printer.HandleEvent) - } - return err - }) + if slices.Contains(attached, event.ID) { + return } + eg.Go(func() error { + ctr, err := s.apiClient().ContainerInspect(ctx, event.ID) + if err != nil { + return err + } + + err = s.doLogContainer(ctx, options.Start.Attach, event.Source, ctr, api.LogOptions{ + Follow: true, + Since: ctr.State.StartedAt, + }) + if errdefs.IsNotImplemented(err) { + // container may be configured with logging_driver: none + // as container already started, we might miss the very first logs. But still better than none + return s.doAttachContainer(ctx, event.Service, event.ID, event.Source, printer.HandleEvent) + } + return err + }) }) eg.Go(func() error {