Restore support for control sequence in `compose run`

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-07-20 11:39:29 +02:00
parent afc9cf8018
commit c257001e5a
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
2 changed files with 97 additions and 61 deletions

View File

@ -24,11 +24,11 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose-cli/pkg/api"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/term"
"github.com/docker/compose-cli/pkg/api"
"github.com/docker/compose-cli/pkg/utils"
)
@ -85,27 +85,10 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, stdin io.ReadCloser, stdout, stderr io.Writer) (func(), chan bool, error) {
detached := make(chan bool)
var (
in *streams.In
restore = func() { /* noop */ }
)
if stdin != nil {
in = streams.NewIn(stdin)
}
streamIn, streamOut, err := s.getContainerStreams(ctx, container)
if err != nil {
return restore, detached, err
}
go func() {
<-ctx.Done()
if in != nil {
in.Close() //nolint:errcheck
}
streamOut.Close() //nolint:errcheck
}()
if in != nil && streamIn != nil {
in := streams.NewIn(stdin)
if in.IsTerminal() {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
@ -115,6 +98,22 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
term.RestoreTerminal(in.FD(), state) //nolint:errcheck
}
}
}
streamIn, streamOut, err := s.getContainerStreams(ctx, container)
if err != nil {
return restore, detached, err
}
go func() {
<-ctx.Done()
if stdin != nil {
stdin.Close() //nolint:errcheck
}
streamOut.Close() //nolint:errcheck
}()
if streamIn != nil && stdin != nil {
go func() {
_, err := io.Copy(streamIn, stdin)
if _, ok := err.(term.EscapeError); ok {

View File

@ -24,9 +24,11 @@ import (
"github.com/docker/compose-cli/pkg/api"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/streams"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/stringid"
"github.com/moby/term"
)
@ -37,11 +39,83 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
return 0, err
}
service, err := project.GetService(opts.Service)
containerID, err := s.prepareRun(ctx, project, observedState, opts)
if err != nil {
return 0, err
}
if opts.Detach {
err := s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
fmt.Fprintln(opts.Stdout, containerID)
return 0, nil
}
r, err := s.getEscapeKeyProxy(opts.Stdin)
if err != nil {
return 0, err
}
stdin, stdout, err := s.getContainerStreams(ctx, containerID)
if err != nil {
return 0, err
}
defer stdin.Close() //nolint:errcheck
defer stdout.Close() //nolint:errcheck
detached := make(chan bool)
in := streams.NewIn(opts.Stdin)
if in.IsTerminal() {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return 0, err
}
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
}
go func() {
if opts.Tty {
io.Copy(opts.Stdout, stdout) //nolint:errcheck
} else {
stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout) //nolint:errcheck
}
}()
go func() {
_, err := io.Copy(stdin, r)
if _, ok := err.(term.EscapeError); ok {
detached <- true
}
}()
err = s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
s.monitorTTySize(ctx, containerID, s.apiClient.ContainerResize)
statusC, errC := s.apiClient.ContainerWait(ctx, containerID, container.WaitConditionNextExit)
select {
case status := <-statusC:
return int(status.StatusCode), nil
case err := <-errC:
return 0, err
case <-detached:
return 0, err
}
}
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, observedState Containers, opts api.RunOptions) (string, error) {
service, err := project.GetService(opts.Service)
if err != nil {
return "", err
}
applyRunOptions(project, &service, opts)
slug := stringid.GenerateRandomID()
@ -58,54 +132,17 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
service.Labels = service.Labels.Add(api.OneoffLabel, "True")
if err := s.ensureImagesExists(ctx, project, observedState, false); err != nil { // all dependencies already checked, but might miss service img
return 0, err
return "", err
}
if err := s.waitDependencies(ctx, project, service); err != nil {
return 0, err
return "", err
}
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove, opts.UseNetworkAliases)
if err != nil {
return 0, err
return "", err
}
containerID := created.ID
if opts.Detach {
err := s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
fmt.Fprintln(opts.Stdout, containerID)
return 0, nil
}
r, err := s.getEscapeKeyProxy(opts.Stdin)
if err != nil {
return 0, err
}
restore, detachC, err := s.attachContainerStreams(ctx, containerID, service.Tty, r, opts.Stdout, opts.Stderr)
if err != nil {
return 0, err
}
defer restore()
statusC, errC := s.apiClient.ContainerWait(context.Background(), containerID, container.WaitConditionNextExit)
err = s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
s.monitorTTySize(ctx, containerID, s.apiClient.ContainerResize)
select {
case status := <-statusC:
return int(status.StatusCode), nil
case <-detachC:
return 0, nil
case err := <-errC:
return 0, err
}
return containerID, nil
}
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser) (io.ReadCloser, error) {