Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit b6552cd

Browse files
committed
add support for detach keys on compose run|exec
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent d20c3b0 commit b6552cd

File tree

4 files changed

+64
-14
lines changed

4 files changed

+64
-14
lines changed

.golangci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ linters:
1515
- gosimple
1616
- govet
1717
- ineffassign
18-
- interfacer
1918
- lll
2019
- misspell
2120
- nakedret

pkg/compose/attach.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/docker/compose-cli/pkg/api"
2828
moby "github.com/docker/docker/api/types"
2929
"github.com/docker/docker/pkg/stdcopy"
30+
"github.com/moby/term"
3031

3132
"github.com/docker/compose-cli/pkg/utils"
3233
)
@@ -77,23 +78,23 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
7778
Line: line,
7879
})
7980
})
80-
_, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w)
81+
_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w)
8182
return err
8283
}
8384

84-
func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, r io.ReadCloser, w io.Writer) (func(), error) {
85+
func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, r io.ReadCloser, w io.Writer) (func(), chan bool, error) {
86+
detached := make(chan bool)
8587
var (
8688
in *streams.In
8789
restore = func() { /* noop */ }
8890
)
8991
if r != nil {
9092
in = streams.NewIn(r)
91-
restore = in.RestoreTerminal
9293
}
9394

9495
stdin, stdout, err := s.getContainerStreams(ctx, container)
9596
if err != nil {
96-
return restore, err
97+
return restore, detached, err
9798
}
9899

99100
go func() {
@@ -105,12 +106,20 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
105106
}()
106107

107108
if in != nil && stdin != nil {
108-
err := in.SetRawTerminal()
109-
if err != nil {
110-
return restore, err
109+
if in.IsTerminal() {
110+
state, err := term.SetRawTerminal(in.FD())
111+
if err != nil {
112+
return restore, detached, err
113+
}
114+
restore = func() {
115+
term.RestoreTerminal(in.FD(), state) //nolint:errcheck
116+
}
111117
}
112118
go func() {
113-
io.Copy(stdin, in) //nolint:errcheck
119+
_, err := io.Copy(stdin, r)
120+
if _, ok := err.(term.EscapeError); ok {
121+
close(detached)
122+
}
114123
}()
115124
}
116125

@@ -123,7 +132,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
123132
}
124133
}()
125134
}
126-
return restore, nil
135+
return restore, detached, nil
127136
}
128137

129138
func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {

pkg/compose/exec.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
"io"
2323

2424
"github.com/compose-spec/compose-go/types"
25+
"github.com/docker/cli/cli/streams"
2526
moby "github.com/docker/docker/api/types"
2627
"github.com/docker/docker/api/types/filters"
2728
"github.com/docker/docker/pkg/stdcopy"
29+
"github.com/moby/term"
2830

2931
"github.com/docker/compose-cli/pkg/api"
3032
)
@@ -92,18 +94,34 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
9294
outputDone := make(chan error)
9395
inputDone := make(chan error)
9496

97+
stdout := ContainerStdout{HijackedResponse: resp}
98+
stdin := ContainerStdin{HijackedResponse: resp}
99+
r, err := s.getEscapeKeyProxy(opts.Reader)
100+
if err != nil {
101+
return err
102+
}
103+
104+
in := streams.NewIn(opts.Reader)
105+
if in.IsTerminal() {
106+
state, err := term.SetRawTerminal(in.FD())
107+
if err != nil {
108+
return err
109+
}
110+
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
111+
}
112+
95113
go func() {
96114
if opts.Tty {
97-
_, err := io.Copy(opts.Writer, resp.Reader)
115+
_, err := io.Copy(opts.Writer, stdout)
98116
outputDone <- err
99117
} else {
100-
_, err := stdcopy.StdCopy(opts.Writer, opts.Writer, resp.Reader)
118+
_, err := stdcopy.StdCopy(opts.Writer, opts.Writer, stdout)
101119
outputDone <- err
102120
}
103121
}()
104122

105123
go func() {
106-
_, err := io.Copy(resp.Conn, opts.Reader)
124+
_, err := io.Copy(stdin, r)
107125
inputDone <- err
108126
}()
109127

@@ -112,6 +130,9 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
112130
case err := <-outputDone:
113131
return err
114132
case err := <-inputDone:
133+
if _, ok := err.(term.EscapeError); ok {
134+
return nil
135+
}
115136
if err != nil {
116137
return err
117138
}

pkg/compose/run.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ package compose
1919
import (
2020
"context"
2121
"fmt"
22+
"io"
2223

2324
"github.com/docker/compose-cli/pkg/api"
2425

2526
"github.com/compose-spec/compose-go/types"
2627
moby "github.com/docker/docker/api/types"
2728
"github.com/docker/docker/api/types/container"
29+
"github.com/docker/docker/pkg/ioutils"
2830
"github.com/docker/docker/pkg/stringid"
31+
"github.com/moby/term"
2932
)
3033

3134
func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
@@ -75,7 +78,11 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
7578
return 0, nil
7679
}
7780

78-
restore, err := s.attachContainerStreams(ctx, containerID, service.Tty, opts.Reader, opts.Writer)
81+
r, err := s.getEscapeKeyProxy(opts.Reader)
82+
if err != nil {
83+
return 0, err
84+
}
85+
restore, detachC, err := s.attachContainerStreams(ctx, containerID, service.Tty, r, opts.Writer)
7986
if err != nil {
8087
return 0, err
8188
}
@@ -93,12 +100,26 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
93100
select {
94101
case status := <-statusC:
95102
return int(status.StatusCode), nil
103+
case <-detachC:
104+
return 0, nil
96105
case err := <-errC:
97106
return 0, err
98107
}
99108

100109
}
101110

111+
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser) (io.ReadCloser, error) {
112+
var escapeKeys = []byte{16, 17}
113+
if s.configFile.DetachKeys != "" {
114+
customEscapeKeys, err := term.ToBytes(s.configFile.DetachKeys)
115+
if err != nil {
116+
return nil, err
117+
}
118+
escapeKeys = customEscapeKeys
119+
}
120+
return ioutils.NewReadCloserWrapper(term.NewEscapeProxy(r, escapeKeys), r.Close), nil
121+
}
122+
102123
func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) {
103124
service.Tty = opts.Tty
104125
service.ContainerName = opts.Name

0 commit comments

Comments
 (0)