docker compose exec to return command exit code

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-04-28 17:59:37 +02:00
parent 38b4220bdb
commit 15eab93b31
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
11 changed files with 95 additions and 32 deletions

View File

@ -227,8 +227,8 @@ func (cs *aciComposeService) Remove(ctx context.Context, project *types.Project,
return nil, errdefs.ErrNotImplemented
}
func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
func (cs *aciComposeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
return nil, errdefs.ErrNotImplemented

View File

@ -92,8 +92,8 @@ func (c *composeService) Remove(ctx context.Context, project *types.Project, opt
return nil, errdefs.ErrNotImplemented
}
func (c *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
func (c *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
func (c *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {

View File

@ -61,7 +61,7 @@ type Service interface {
// Remove executes the equivalent to a `compose rm`
Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error)
// 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(ctx context.Context, project string, options PauseOptions) error
// UnPause executes the equivalent to a `compose unpause`

View File

@ -108,7 +108,7 @@ func (s *ServiceDelegator) Remove(ctx context.Context, project *types.Project, o
}
//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)
}

View File

@ -108,8 +108,8 @@ func (s NoImpl) Remove(ctx context.Context, project *types.Project, options Remo
}
//Exec implements Service interface
func (s NoImpl) Exec(ctx context.Context, project *types.Project, options RunOptions) error {
return errdefs.ErrNotImplemented
func (s NoImpl) Exec(ctx context.Context, project *types.Project, opts RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
//Pause implements Service interface

View File

@ -22,6 +22,7 @@ import (
"os"
"github.com/containerd/console"
"github.com/docker/cli/cli"
"github.com/spf13/cobra"
"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.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
}

View File

@ -25,6 +25,6 @@ import (
"github.com/docker/compose-cli/api/errdefs"
)
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
func (b *ecsAPIService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}

View File

@ -184,8 +184,8 @@ func (e ecsLocalSimulation) Remove(ctx context.Context, project *types.Project,
return e.compose.Remove(ctx, project, options)
}
func (e ecsLocalSimulation) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
func (e ecsLocalSimulation) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
func (e ecsLocalSimulation) Pause(ctx context.Context, project string, options compose.PauseOptions) error {

View File

@ -248,8 +248,8 @@ func (s *composeService) Remove(ctx context.Context, project *types.Project, opt
}
// Exec executes a command in a running service container
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
return errdefs.ErrNotImplemented
func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
func (s *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {

View File

@ -28,10 +28,10 @@ import (
"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)
if err != nil {
return err
return 0, err
}
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 {
return err
return 0, err
}
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]
@ -63,11 +63,11 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
AttachStderr: true,
})
if err != nil {
return err
return 0, err
}
if opts.Detach {
return s.apiClient.ContainerExecStart(ctx, exec.ID, apitypes.ExecStartCheck{
return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, apitypes.ExecStartCheck{
Detach: true,
Tty: opts.Tty,
})
@ -78,19 +78,19 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
Tty: opts.Tty,
})
if err != nil {
return err
return 0, err
}
defer resp.Close()
if opts.Tty {
s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
if err != nil {
return err
return 0, err
}
}
readChannel := make(chan error, 10)
writeChannel := make(chan error, 10)
readChannel := make(chan error)
writeChannel := make(chan error)
go func() {
_, err := io.Copy(opts.Writer, resp.Reader)
@ -102,12 +102,23 @@ func (s *composeService) Exec(ctx context.Context, project *types.Project, opts
writeChannel <- err
}()
for {
select {
case err := <-readChannel:
return err
case err := <-writeChannel:
return err
}
select {
case err = <-readChannel:
break
case err = <-writeChannel:
break
}
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
}

View 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})
})
}