Merge pull request #372 from docker/feat-non-interactive-exec

Allow non-interactive exec on ACI
This commit is contained in:
Ulysses Souza 2020-07-09 11:51:35 +02:00 committed by GitHub
commit b22118a861
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 42 deletions

View File

@ -36,6 +36,7 @@ import (
"github.com/docker/api/azure/convert" "github.com/docker/api/azure/convert"
"github.com/docker/api/azure/login" "github.com/docker/api/azure/login"
"github.com/docker/api/containers"
"github.com/docker/api/context/store" "github.com/docker/api/context/store"
"github.com/docker/api/progress" "github.com/docker/api/progress"
) )
@ -166,7 +167,7 @@ func getTermSize() (*int32, *int32) {
return to.Int32Ptr(int32(rows)), to.Int32Ptr(int32(cols)) return to.Int32Ptr(int32(rows)), to.Int32Ptr(int32(cols))
} }
func exec(ctx context.Context, address string, password string, reader io.Reader, writer io.Writer) error { func exec(ctx context.Context, address string, password string, request containers.ExecRequest) error {
conn, _, _, err := ws.DefaultDialer.Dial(ctx, address) conn, _, _, err := ws.DefaultDialer.Dial(ctx, address)
if err != nil { if err != nil {
return err return err
@ -190,34 +191,36 @@ func exec(ctx context.Context, address string, password string, reader io.Reader
downstreamChannel <- err downstreamChannel <- err
return return
} }
fmt.Fprint(writer, string(msg)) fmt.Fprint(request.Stdout, string(msg))
} }
}() }()
go func() { if request.Interactive {
for { go func() {
// We send each byte, byte-per-byte over the for {
// websocket because the console is in raw mode // We send each byte, byte-per-byte over the
buffer := make([]byte, 1) // websocket because the console is in raw mode
n, err := reader.Read(buffer) buffer := make([]byte, 1)
if err != nil { n, err := request.Stdin.Read(buffer)
if err == io.EOF {
upstreamChannel <- nil
return
}
upstreamChannel <- err
return
}
if n > 0 {
err := wsutil.WriteClientMessage(conn, ws.OpText, buffer)
if err != nil { if err != nil {
if err == io.EOF {
upstreamChannel <- nil
return
}
upstreamChannel <- err upstreamChannel <- err
return return
} }
if n > 0 {
err := wsutil.WriteClientMessage(conn, ws.OpText, buffer)
if err != nil {
upstreamChannel <- err
return
}
}
} }
} }()
}() }
for { for {
select { select {

View File

@ -19,7 +19,6 @@ package azure
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -192,13 +191,13 @@ func getGroupAndContainerName(containerID string) (groupName string, containerNa
return groupName, containerName return groupName, containerName
} }
func (cs *aciContainerService) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error { func (cs *aciContainerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
err := verifyExecCommand(command) err := verifyExecCommand(request.Command)
if err != nil { if err != nil {
return err return err
} }
groupName, containerAciName := getGroupAndContainerName(name) groupName, containerAciName := getGroupAndContainerName(name)
containerExecResponse, err := execACIContainer(ctx, cs.ctx, command, groupName, containerAciName) containerExecResponse, err := execACIContainer(ctx, cs.ctx, request.Command, groupName, containerAciName)
if err != nil { if err != nil {
return err return err
} }
@ -207,8 +206,7 @@ func (cs *aciContainerService) Exec(ctx context.Context, name string, command st
context.Background(), context.Background(),
*containerExecResponse.WebSocketURI, *containerExecResponse.WebSocketURI,
*containerExecResponse.Password, *containerExecResponse.Password,
reader, request,
writer,
) )
} }

View File

@ -27,10 +27,12 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/api/client" "github.com/docker/api/client"
"github.com/docker/api/containers"
) )
type execOpts struct { type execOpts struct {
Tty bool tty bool
interactive bool
} }
// ExecCommand runs a command in a running container // ExecCommand runs a command in a running container
@ -45,8 +47,8 @@ func ExecCommand() *cobra.Command {
}, },
} }
cmd.Flags().BoolVarP(&opts.Tty, "tty", "t", false, "Allocate a pseudo-TTY") cmd.Flags().BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
cmd.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached") cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
return cmd return cmd
} }
@ -57,7 +59,16 @@ func runExec(ctx context.Context, opts execOpts, name string, command string) er
return errors.Wrap(err, "cannot connect to backend") return errors.Wrap(err, "cannot connect to backend")
} }
if opts.Tty { request := containers.ExecRequest{
Command: command,
Tty: opts.tty,
Interactive: opts.interactive,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if opts.tty {
con := console.Current() con := console.Current()
if err := con.SetRaw(); err != nil { if err := con.SetRaw(); err != nil {
return err return err
@ -67,7 +78,11 @@ func runExec(ctx context.Context, opts execOpts, name string, command string) er
fmt.Println("Unable to close the console") fmt.Println("Unable to close the console")
} }
}() }()
return c.ContainerService().Exec(ctx, name, command, con, con)
request.Stdin = con
request.Stdout = con
request.Stderr = con
} }
return c.ContainerService().Exec(ctx, name, command, os.Stdin, os.Stdout)
return c.ContainerService().Exec(ctx, name, request)
} }

View File

@ -72,6 +72,16 @@ type ContainerConfig struct {
Environment []string Environment []string
} }
// ExecRequest contaiens configuration about an exec request
type ExecRequest struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Command string
Interactive bool
Tty bool
}
// LogsRequest contains configuration about a log request // LogsRequest contains configuration about a log request
type LogsRequest struct { type LogsRequest struct {
Follow bool Follow bool
@ -88,7 +98,7 @@ type Service interface {
// Run creates and starts a container // Run creates and starts a container
Run(ctx context.Context, config ContainerConfig) error Run(ctx context.Context, config ContainerConfig) error
// Exec executes a command inside a running container // Exec executes a command inside a running container
Exec(ctx context.Context, containerName string, command string, reader io.Reader, writer io.Writer) error Exec(ctx context.Context, containerName string, request ExecRequest) error
// Logs returns all the logs of a container // Logs returns all the logs of a container
Logs(ctx context.Context, containerName string, request LogsRequest) error Logs(ctx context.Context, containerName string, request LogsRequest) error
// Delete removes containers // Delete removes containers

View File

@ -22,7 +22,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/cli"
@ -95,8 +94,8 @@ func (cs *containerService) Stop(ctx context.Context, containerName string, time
return errors.New("not implemented") return errors.New("not implemented")
} }
func (cs *containerService) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error { func (cs *containerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
fmt.Printf("Executing command %q on container %q", command, name) fmt.Printf("Executing command %q on container %q", request.Command, name)
return nil return nil
} }

View File

@ -179,9 +179,9 @@ func (ms *local) Stop(ctx context.Context, containerID string, timeout *uint32)
return ms.apiClient.ContainerStop(ctx, containerID, t) return ms.apiClient.ContainerStop(ctx, containerID, t)
} }
func (ms *local) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error { func (ms *local) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
cec, err := ms.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{ cec, err := ms.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{
Cmd: []string{command}, Cmd: []string{request.Command},
Tty: true, Tty: true,
AttachStdin: true, AttachStdin: true,
AttachStdout: true, AttachStdout: true,
@ -202,12 +202,12 @@ func (ms *local) Exec(ctx context.Context, name string, command string, reader i
writeChannel := make(chan error, 10) writeChannel := make(chan error, 10)
go func() { go func() {
_, err := io.Copy(writer, resp.Reader) _, err := io.Copy(request.Stdout, resp.Reader)
readChannel <- err readChannel <- err
}() }()
go func() { go func() {
_, err := io.Copy(resp.Conn, reader) _, err := io.Copy(resp.Conn, request.Stdin)
writeChannel <- err writeChannel <- err
}() }()

View File

@ -92,7 +92,12 @@ func (p *proxy) Exec(ctx context.Context, request *containersv1.ExecRequest) (*c
Stream: stream, Stream: stream,
} }
return &containersv1.ExecResponse{}, Client(ctx).ContainerService().Exec(ctx, request.GetId(), request.GetCommand(), io, io) return &containersv1.ExecResponse{}, Client(ctx).ContainerService().Exec(ctx, request.GetId(), containers.ExecRequest{
Stdin: io,
Stdout: io,
Command: request.GetCommand(),
Tty: request.GetTty(),
})
} }
func (p *proxy) Logs(request *containersv1.LogsRequest, stream containersv1.Containers_LogsServer) error { func (p *proxy) Logs(request *containersv1.LogsRequest, stream containersv1.Containers_LogsServer) error {

View File

@ -105,6 +105,9 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
}) })
s.Step("exec command", func() { s.Step("exec command", func() {
output := s.NewDockerCommand("exec", testContainerName, "pwd").ExecOrDie()
Expect(output).To(ContainSubstring("/"))
_, err := s.NewDockerCommand("exec", testContainerName, "echo", "fail_with_argument").Exec() _, err := s.NewDockerCommand("exec", testContainerName, "echo", "fail_with_argument").Exec()
Expect(err.Error()).To(ContainSubstring("ACI exec command does not accept arguments to the command. " + Expect(err.Error()).To(ContainSubstring("ACI exec command does not accept arguments to the command. " +
"Only the binary should be specified")) "Only the binary should be specified"))