mirror of
https://github.com/docker/compose.git
synced 2025-07-23 13:45:00 +02:00
Merge pull request #372 from docker/feat-non-interactive-exec
Allow non-interactive exec on ACI
This commit is contained in:
commit
b22118a861
45
azure/aci.go
45
azure/aci.go
@ -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 {
|
||||||
|
@ -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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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"))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user