diff --git a/api/compose/api.go b/api/compose/api.go index 0ad6e1a51..ba043532c 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -186,8 +186,8 @@ type RunOptions struct { Entrypoint []string Detach bool AutoRemove bool - Writer io.Writer - Reader io.Reader + Writer io.WriteCloser + Reader io.ReadCloser Tty bool WorkingDir string User string diff --git a/local/compose/attach.go b/local/compose/attach.go index e8d82e605..9fd695fdb 100644 --- a/local/compose/attach.go +++ b/local/compose/attach.go @@ -27,6 +27,7 @@ import ( "github.com/docker/compose-cli/utils" "github.com/compose-spec/compose-go/types" + "github.com/docker/cli/cli/streams" moby "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stdcopy" ) @@ -108,26 +109,40 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con Service: container.Labels[serviceLabel], }) - return s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w) + _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w) + return err } -func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, r io.Reader, w io.Writer) error { +func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, r io.ReadCloser, w io.Writer) (func(), error) { + var ( + in *streams.In + restore = func() { /* noop */ } + ) + if r != nil { + in = streams.NewIn(r) + restore = in.RestoreTerminal + } + stdin, stdout, err := s.getContainerStreams(ctx, container) if err != nil { - return err + return restore, err } go func() { <-ctx.Done() - stdout.Close() //nolint:errcheck - if stdin != nil { - stdin.Close() //nolint:errcheck + if in != nil { + in.Close() //nolint:errcheck } + stdout.Close() //nolint:errcheck }() - if r != nil && stdin != nil { + if in != nil && stdin != nil { + err := in.SetRawTerminal() + if err != nil { + return restore, err + } go func() { - io.Copy(stdin, r) //nolint:errcheck + io.Copy(stdin, in) //nolint:errcheck }() } @@ -140,7 +155,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s } }() } - return nil + return restore, nil } func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) { diff --git a/local/compose/create.go b/local/compose/create.go index e693f14cc..2c68e80ee 100644 --- a/local/compose/create.go +++ b/local/compose/create.go @@ -249,7 +249,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, ExposedPorts: buildContainerPorts(service), Tty: tty, OpenStdin: stdinOpen, - StdinOnce: true, + StdinOnce: attachStdin && stdinOpen, AttachStdin: attachStdin, AttachStderr: true, AttachStdout: true, diff --git a/local/compose/run.go b/local/compose/run.go index af5c2249b..71c57672f 100644 --- a/local/compose/run.go +++ b/local/compose/run.go @@ -86,10 +86,11 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types. return 0, err } oneoffContainer := containers[0] - err = s.attachContainerStreams(ctx, oneoffContainer.ID, service.Tty, opts.Reader, opts.Writer) + restore, err := s.attachContainerStreams(ctx, oneoffContainer.ID, service.Tty, opts.Reader, opts.Writer) if err != nil { return 0, err } + defer restore() err = s.apiClient.ContainerStart(ctx, containerID, apitypes.ContainerStartOptions{}) if err != nil { diff --git a/utils/logs.go b/utils/logs.go index d1e76df86..1c918d751 100644 --- a/utils/logs.go +++ b/utils/logs.go @@ -62,7 +62,7 @@ func (a *allowListLogConsumer) Register(name string) { } // GetWriter creates a io.Writer that will actually split by line and format by LogConsumer -func GetWriter(container, service string, events compose.ContainerEventListener) io.Writer { +func GetWriter(container, service string, events compose.ContainerEventListener) io.WriteCloser { return &splitBuffer{ buffer: bytes.Buffer{}, service: service, @@ -100,3 +100,17 @@ func (s *splitBuffer) Write(b []byte) (int, error) { } return n, nil } + +func (s *splitBuffer) Close() error { + b := s.buffer.Bytes() + if len(b) == 0 { + return nil + } + s.consumer(compose.ContainerEvent{ + Type: compose.ContainerEventLog, + Service: s.service, + Container: s.container, + Line: string(b), + }) + return nil +}