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
Родитель 38b4220bdb
Коммит 15eab93b31
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9858809D6F8F6E7E
11 изменённых файлов: 95 добавлений и 32 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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`

Просмотреть файл

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

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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
}

Просмотреть файл

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