mirror of
https://github.com/docker/compose.git
synced 2025-07-23 21:54:40 +02:00
docker compose exec to return command exit code
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
38b4220bdb
commit
15eab93b31
@ -227,8 +227,8 @@ func (cs *aciComposeService) Remove(ctx context.Context, project *types.Project,
|
|||||||
return nil, errdefs.ErrNotImplemented
|
return nil, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
func (cs *aciComposeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
|
func (cs *aciComposeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
|
||||||
return nil, errdefs.ErrNotImplemented
|
return nil, errdefs.ErrNotImplemented
|
||||||
|
@ -92,8 +92,8 @@ func (c *composeService) Remove(ctx context.Context, project *types.Project, opt
|
|||||||
return nil, errdefs.ErrNotImplemented
|
return nil, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (c *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
func (c *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
||||||
|
@ -61,7 +61,7 @@ type Service interface {
|
|||||||
// Remove executes the equivalent to a `compose rm`
|
// Remove executes the equivalent to a `compose rm`
|
||||||
Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error)
|
Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error)
|
||||||
// Exec executes a command in a running service container
|
// Exec executes a command in a running service container
|
||||||
Exec(ctx context.Context, project *types.Project, opts RunOptions) error
|
Exec(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||||
// Pause executes the equivalent to a `compose pause`
|
// Pause executes the equivalent to a `compose pause`
|
||||||
Pause(ctx context.Context, project string, options PauseOptions) error
|
Pause(ctx context.Context, project string, options PauseOptions) error
|
||||||
// UnPause executes the equivalent to a `compose unpause`
|
// UnPause executes the equivalent to a `compose unpause`
|
||||||
|
@ -108,7 +108,7 @@ func (s *ServiceDelegator) Remove(ctx context.Context, project *types.Project, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Exec implements Service interface
|
//Exec implements Service interface
|
||||||
func (s *ServiceDelegator) Exec(ctx context.Context, project *types.Project, options RunOptions) error {
|
func (s *ServiceDelegator) Exec(ctx context.Context, project *types.Project, options RunOptions) (int, error) {
|
||||||
return s.Delegate.Exec(ctx, project, options)
|
return s.Delegate.Exec(ctx, project, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +108,8 @@ func (s NoImpl) Remove(ctx context.Context, project *types.Project, options Remo
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Exec implements Service interface
|
//Exec implements Service interface
|
||||||
func (s NoImpl) Exec(ctx context.Context, project *types.Project, options RunOptions) error {
|
func (s NoImpl) Exec(ctx context.Context, project *types.Project, opts RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
//Pause implements Service interface
|
//Pause implements Service interface
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/compose"
|
"github.com/docker/compose-cli/api/compose"
|
||||||
@ -108,5 +109,13 @@ func runExec(ctx context.Context, backend compose.Service, opts execOpts) error
|
|||||||
execOpts.Writer = con
|
execOpts.Writer = con
|
||||||
execOpts.Reader = con
|
execOpts.Reader = con
|
||||||
}
|
}
|
||||||
return backend.Exec(ctx, project, execOpts)
|
exitCode, err := backend.Exec(ctx, project, execOpts)
|
||||||
|
if exitCode != 0 {
|
||||||
|
errMsg := ""
|
||||||
|
if err != nil {
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,6 @@ import (
|
|||||||
"github.com/docker/compose-cli/api/errdefs"
|
"github.com/docker/compose-cli/api/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
@ -184,8 +184,8 @@ func (e ecsLocalSimulation) Remove(ctx context.Context, project *types.Project,
|
|||||||
return e.compose.Remove(ctx, project, options)
|
return e.compose.Remove(ctx, project, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ecsLocalSimulation) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (e ecsLocalSimulation) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ecsLocalSimulation) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
func (e ecsLocalSimulation) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
||||||
|
@ -248,8 +248,8 @@ func (s *composeService) Remove(ctx context.Context, project *types.Project, opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes a command in a running service container
|
// Exec executes a command in a running service container
|
||||||
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
return errdefs.ErrNotImplemented
|
return 0, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
func (s *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
|
||||||
|
@ -28,10 +28,10 @@ import (
|
|||||||
"github.com/docker/compose-cli/api/compose"
|
"github.com/docker/compose-cli/api/compose"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
|
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||||
service, err := project.GetService(opts.Service)
|
service, err := project.GetService(opts.Service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
containers, err := s.apiClient.ContainerList(ctx, apitypes.ContainerListOptions{
|
containers, err := s.apiClient.ContainerList(ctx, apitypes.ContainerListOptions{
|
||||||
@ -42,10 +42,10 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
if len(containers) < 1 {
|
if len(containers) < 1 {
|
||||||
return fmt.Errorf("container %s not running", getContainerName(project.Name, service, opts.Index))
|
return 0, fmt.Errorf("container %s not running", getContainerName(project.Name, service, opts.Index))
|
||||||
}
|
}
|
||||||
container := containers[0]
|
container := containers[0]
|
||||||
|
|
||||||
@ -63,11 +63,11 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
|
|||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Detach {
|
if opts.Detach {
|
||||||
return s.apiClient.ContainerExecStart(ctx, exec.ID, apitypes.ExecStartCheck{
|
return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, apitypes.ExecStartCheck{
|
||||||
Detach: true,
|
Detach: true,
|
||||||
Tty: opts.Tty,
|
Tty: opts.Tty,
|
||||||
})
|
})
|
||||||
@ -78,19 +78,19 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
|
|||||||
Tty: opts.Tty,
|
Tty: opts.Tty,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
|
|
||||||
if opts.Tty {
|
if opts.Tty {
|
||||||
s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
|
s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readChannel := make(chan error, 10)
|
readChannel := make(chan error)
|
||||||
writeChannel := make(chan error, 10)
|
writeChannel := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := io.Copy(opts.Writer, resp.Reader)
|
_, err := io.Copy(opts.Writer, resp.Reader)
|
||||||
@ -102,12 +102,23 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
|
|||||||
writeChannel <- err
|
writeChannel <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
select {
|
||||||
select {
|
case err = <-readChannel:
|
||||||
case err := <-readChannel:
|
break
|
||||||
return err
|
case err = <-writeChannel:
|
||||||
case err := <-writeChannel:
|
break
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return s.getExecExitStatus(ctx, exec.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) {
|
||||||
|
resp, err := s.apiClient.ContainerExecInspect(ctx, execID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return resp.ExitCode, nil
|
||||||
}
|
}
|
||||||
|
43
local/e2e/compose/compose_exec_test.go
Normal file
43
local/e2e/compose/compose_exec_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/icmd"
|
||||||
|
|
||||||
|
. "github.com/docker/compose-cli/utils/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalComposeExec(t *testing.T) {
|
||||||
|
c := NewParallelE2eCLI(t, binDir)
|
||||||
|
|
||||||
|
const projectName = "compose-e2e-exec"
|
||||||
|
|
||||||
|
c.RunDockerCmd("compose", "--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
|
||||||
|
|
||||||
|
t.Run("exec true", func(t *testing.T) {
|
||||||
|
res := c.RunDockerOrExitError("exec", "compose-e2e-exec_simple_1", "/bin/true")
|
||||||
|
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("exec false", func(t *testing.T) {
|
||||||
|
res := c.RunDockerOrExitError("exec", "compose-e2e-exec_simple_1", "/bin/false")
|
||||||
|
res.Assert(t, icmd.Expected{ExitCode: 1})
|
||||||
|
})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user