@@ -22,8 +22,9 @@ import (
2222 "io"
2323
2424 "github.com/compose-spec/compose-go/types"
25- apitypes "github.com/docker/docker/api/types"
25+ moby "github.com/docker/docker/api/types"
2626 "github.com/docker/docker/api/types/filters"
27+ "github.com/docker/docker/pkg/stdcopy"
2728
2829 "github.com/docker/compose-cli/api/compose"
2930)
@@ -34,34 +35,14 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
3435 return 0 , err
3536 }
3637
37- containers , err := s .apiClient .ContainerList (ctx , apitypes.ContainerListOptions {
38- Filters : filters .NewArgs (
39- projectFilter (project .Name ),
40- serviceFilter (service .Name ),
41- filters .Arg ("label" , fmt .Sprintf ("%s=%d" , containerNumberLabel , opts .Index )),
42- ),
43- })
38+ container , err := s .getExecTarget (ctx , project , service , opts )
4439 if err != nil {
4540 return 0 , err
4641 }
47- if len (containers ) < 1 {
48- return 0 , fmt .Errorf ("container %s not running" , getContainerName (project .Name , service , opts .Index ))
49- }
50- container := containers [0 ]
51-
52- var env []string
53- for k , v := range service .Environment .OverrideBy (types .NewMappingWithEquals (opts .Environment )).
54- Resolve (func (s string ) (string , bool ) {
55- v , ok := project .Environment [s ]
56- return v , ok
57- }).
58- RemoveEmpty () {
59- env = append (env , fmt .Sprintf ("%s=%s" , k , * v ))
60- }
6142
62- exec , err := s .apiClient .ContainerExecCreate (ctx , container .ID , apitypes .ExecConfig {
43+ exec , err := s .apiClient .ContainerExecCreate (ctx , container .ID , moby .ExecConfig {
6344 Cmd : opts .Command ,
64- Env : env ,
45+ Env : s . getExecEnvironment ( project , service , opts ) ,
6546 User : opts .User ,
6647 Privileged : opts .Privileged ,
6748 Tty : opts .Tty ,
@@ -77,15 +58,14 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
7758 }
7859
7960 if opts .Detach {
80- return 0 , s .apiClient .ContainerExecStart (ctx , exec .ID , apitypes .ExecStartCheck {
61+ return 0 , s .apiClient .ContainerExecStart (ctx , exec .ID , moby .ExecStartCheck {
8162 Detach : true ,
8263 Tty : opts .Tty ,
8364 })
8465 }
8566
86- resp , err := s .apiClient .ContainerExecAttach (ctx , exec .ID , apitypes.ExecStartCheck {
87- Detach : false ,
88- Tty : opts .Tty ,
67+ resp , err := s .apiClient .ContainerExecAttach (ctx , exec .ID , moby.ExecStartCheck {
68+ Tty : opts .Tty ,
8969 })
9070 if err != nil {
9171 return 0 , err
@@ -99,30 +79,78 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
9979 }
10080 }
10181
102- readChannel := make (chan error )
103- writeChannel := make (chan error )
82+ err = s .interactiveExec (ctx , opts , resp )
83+ if err != nil {
84+ return 0 , err
85+ }
86+
87+ return s .getExecExitStatus (ctx , exec .ID )
88+ }
89+
90+ // inspired by https://github.com/docker/cli/blob/master/cli/command/container/exec.go#L116
91+ func (s * composeService ) interactiveExec (ctx context.Context , opts compose.RunOptions , resp moby.HijackedResponse ) error {
92+ outputDone := make (chan error )
93+ inputDone := make (chan error )
10494
10595 go func () {
106- _ , err := io .Copy (opts .Writer , resp .Reader )
107- readChannel <- err
96+ if opts .Tty {
97+ _ , err := io .Copy (opts .Writer , resp .Reader )
98+ outputDone <- err
99+ } else {
100+ _ , err := stdcopy .StdCopy (opts .Writer , opts .Writer , resp .Reader )
101+ outputDone <- err
102+ }
108103 }()
109104
110105 go func () {
111106 _ , err := io .Copy (resp .Conn , opts .Reader )
112- writeChannel <- err
107+ inputDone <- err
113108 }()
114109
115- select {
116- case err = <- readChannel :
117- break
118- case err = <- writeChannel :
119- break
110+ for {
111+ select {
112+ case err := <- outputDone :
113+ return err
114+ case err := <- inputDone :
115+ if err != nil {
116+ return err
117+ }
118+ // Wait for output to complete streaming
119+ case <- ctx .Done ():
120+ return ctx .Err ()
121+ }
120122 }
123+ }
121124
125+ func (s * composeService ) getExecTarget (ctx context.Context , project * types.Project , service types.ServiceConfig , opts compose.RunOptions ) (moby.Container , error ) {
126+ containers , err := s .apiClient .ContainerList (ctx , moby.ContainerListOptions {
127+ Filters : filters .NewArgs (
128+ projectFilter (project .Name ),
129+ serviceFilter (service .Name ),
130+ filters .Arg ("label" , fmt .Sprintf ("%s=%d" , containerNumberLabel , opts .Index )),
131+ ),
132+ })
122133 if err != nil {
123- return 0 , err
134+ return moby. Container {} , err
124135 }
125- return s .getExecExitStatus (ctx , exec .ID )
136+ if len (containers ) < 1 {
137+ return moby.Container {}, fmt .Errorf ("container %s not running" , getContainerName (project .Name , service , opts .Index ))
138+ }
139+ container := containers [0 ]
140+ return container , nil
141+ }
142+
143+ func (s * composeService ) getExecEnvironment (project * types.Project , service types.ServiceConfig , opts compose.RunOptions ) []string {
144+ var env []string
145+ for k , v := range service .Environment .OverrideBy (types .NewMappingWithEquals (opts .Environment )).
146+ Resolve (func (s string ) (string , bool ) {
147+ v , ok := project .Environment [s ]
148+ return v , ok
149+ }).
150+ RemoveEmpty () {
151+ env = append (env , fmt .Sprintf ("%s=%s" , k , * v ))
152+ }
153+ return env
126154}
127155
128156func (s * composeService ) getExecExitStatus (ctx context.Context , execID string ) (int , error ) {
0 commit comments