mirror of
https://github.com/containers/podman.git
synced 2025-06-22 18:08:11 +08:00
Merge pull request #3624 from haircommander/conmon-exec-with-remote-exec
Add remote exec
This commit is contained in:
2
API.md
2
API.md
@ -1591,6 +1591,8 @@ user [?string](#?string)
|
|||||||
workdir [?string](#?string)
|
workdir [?string](#?string)
|
||||||
|
|
||||||
env [?[]string](#?[]string)
|
env [?[]string](#?[]string)
|
||||||
|
|
||||||
|
detachKeys [?string](#?string)
|
||||||
### <a name="Image"></a>type Image
|
### <a name="Image"></a>type Image
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||||
"github.com/containers/libpod/libpod/define"
|
|
||||||
"github.com/containers/libpod/pkg/adapter"
|
"github.com/containers/libpod/pkg/adapter"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -70,11 +69,5 @@ func execCmd(c *cliconfig.ExecValues) error {
|
|||||||
defer runtime.DeferredShutdown(false)
|
defer runtime.DeferredShutdown(false)
|
||||||
|
|
||||||
exitCode, err = runtime.ExecContainer(getContext(), c)
|
exitCode, err = runtime.ExecContainer(getContext(), c)
|
||||||
if errors.Cause(err) == define.ErrOCIRuntimePermissionDenied {
|
|
||||||
exitCode = 126
|
|
||||||
}
|
|
||||||
if errors.Cause(err) == define.ErrOCIRuntimeNotFound {
|
|
||||||
exitCode = 127
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -514,7 +514,9 @@ type ExecOpts(
|
|||||||
# workdir to run command in container
|
# workdir to run command in container
|
||||||
workdir: ?string,
|
workdir: ?string,
|
||||||
# slice of keyword=value environment variables
|
# slice of keyword=value environment variables
|
||||||
env: ?[]string
|
env: ?[]string,
|
||||||
|
# string of detach keys
|
||||||
|
detachKeys: ?string
|
||||||
)
|
)
|
||||||
|
|
||||||
# GetVersion returns version and build information of the podman service
|
# GetVersion returns version and build information of the podman service
|
||||||
|
@ -18,11 +18,6 @@ import (
|
|||||||
"k8s.io/client-go/tools/remotecommand"
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultExecExitCode = 125
|
|
||||||
defaultExecExitCodeCannotInvoke = 126
|
|
||||||
)
|
|
||||||
|
|
||||||
// Init creates a container in the OCI runtime
|
// Init creates a container in the OCI runtime
|
||||||
func (c *Container) Init(ctx context.Context) (err error) {
|
func (c *Container) Init(ctx context.Context) (err error) {
|
||||||
span, _ := opentracing.StartSpanFromContext(ctx, "containerInit")
|
span, _ := opentracing.StartSpanFromContext(ctx, "containerInit")
|
||||||
@ -234,7 +229,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
|
|||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
if err := c.syncContainer(); err != nil {
|
if err := c.syncContainer(); err != nil {
|
||||||
return defaultExecExitCodeCannotInvoke, err
|
return define.ExecErrorCodeCannotInvoke, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +237,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
|
|||||||
|
|
||||||
// TODO can probably relax this once we track exec sessions
|
// TODO can probably relax this once we track exec sessions
|
||||||
if conState != define.ContainerStateRunning {
|
if conState != define.ContainerStateRunning {
|
||||||
return defaultExecExitCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running")
|
return define.ExecErrorCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running")
|
||||||
}
|
}
|
||||||
|
|
||||||
if privileged || c.config.Privileged {
|
if privileged || c.config.Privileged {
|
||||||
@ -269,7 +264,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
|
|||||||
|
|
||||||
logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID)
|
logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID)
|
||||||
if err := c.createExecBundle(sessionID); err != nil {
|
if err := c.createExecBundle(sessionID); err != nil {
|
||||||
return defaultExecExitCodeCannotInvoke, err
|
return define.ExecErrorCodeCannotInvoke, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -281,7 +276,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
|
|||||||
|
|
||||||
pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys)
|
pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec := defaultExecExitCode
|
ec := define.ExecErrorCodeGeneric
|
||||||
// Conmon will pass a non-zero exit code from the runtime as a pid here.
|
// Conmon will pass a non-zero exit code from the runtime as a pid here.
|
||||||
// we differentiate a pid with an exit code by sending it as negative, so reverse
|
// we differentiate a pid with an exit code by sending it as negative, so reverse
|
||||||
// that change and return the exit code the runtime failed with.
|
// that change and return the exit code the runtime failed with.
|
||||||
@ -303,7 +298,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
|
|||||||
if err := c.save(); err != nil {
|
if err := c.save(); err != nil {
|
||||||
// Now we have a PID but we can't save it in the DB
|
// Now we have a PID but we can't save it in the DB
|
||||||
// TODO handle this better
|
// TODO handle this better
|
||||||
return defaultExecExitCode, errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID())
|
return define.ExecErrorCodeGeneric, errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID())
|
||||||
}
|
}
|
||||||
c.newContainerEvent(events.Exec)
|
c.newContainerEvent(events.Exec)
|
||||||
logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID())
|
logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID())
|
||||||
|
30
libpod/define/exec_codes.go
Normal file
30
libpod/define/exec_codes.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package define
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ExecErrorCodeGeneric is the default error code to return from an exec session if libpod failed
|
||||||
|
// prior to calling the runtime
|
||||||
|
ExecErrorCodeGeneric = 125
|
||||||
|
// ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command
|
||||||
|
// an example of this can be found by trying to execute a directory:
|
||||||
|
// `podman exec -l /etc`
|
||||||
|
ExecErrorCodeCannotInvoke = 126
|
||||||
|
// ExecErrorCodeNotFound is the error code to return when a command cannot be found
|
||||||
|
ExecErrorCodeNotFound = 127
|
||||||
|
)
|
||||||
|
|
||||||
|
// TranslateExecErrorToExitCode takes an error and checks whether it
|
||||||
|
// has a predefined exit code associated. If so, it returns that, otherwise it returns
|
||||||
|
// the exit code originally stated in libpod.Exec()
|
||||||
|
func TranslateExecErrorToExitCode(originalEC int, err error) int {
|
||||||
|
if errors.Cause(err) == ErrOCIRuntimePermissionDenied {
|
||||||
|
return ExecErrorCodeCannotInvoke
|
||||||
|
}
|
||||||
|
if errors.Cause(err) == ErrOCIRuntimeNotFound {
|
||||||
|
return ExecErrorCodeNotFound
|
||||||
|
}
|
||||||
|
return originalEC
|
||||||
|
}
|
@ -1000,7 +1000,8 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal
|
|||||||
streams.AttachOutput = true
|
streams.AttachOutput = true
|
||||||
streams.AttachError = true
|
streams.AttachError = true
|
||||||
|
|
||||||
return ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys)
|
ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys)
|
||||||
|
return define.TranslateExecErrorToExitCode(ec, err), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune removes stopped containers
|
// Prune removes stopped containers
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -555,93 +556,6 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
|
|||||||
return psContainers, nil
|
return psContainers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
|
|
||||||
var (
|
|
||||||
oldTermState *term.State
|
|
||||||
)
|
|
||||||
errChan := make(chan error)
|
|
||||||
spec, err := r.Spec(cid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resize := make(chan remotecommand.TerminalSize, 5)
|
|
||||||
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
||||||
|
|
||||||
// Check if we are attached to a terminal. If we are, generate resize
|
|
||||||
// events, and set the terminal to raw mode
|
|
||||||
if haveTerminal && spec.Process.Terminal {
|
|
||||||
logrus.Debugf("Handling terminal attach")
|
|
||||||
|
|
||||||
subCtx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
resizeTty(subCtx, resize)
|
|
||||||
oldTermState, err = term.SaveState(os.Stdin.Fd())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "unable to save terminal state")
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.SetFormatter(&RawTtyFormatter{})
|
|
||||||
term.SetRawTerminal(os.Stdin.Fd())
|
|
||||||
|
|
||||||
}
|
|
||||||
// TODO add detach keys support
|
|
||||||
reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
|
|
||||||
if err != nil {
|
|
||||||
restoreTerminal(oldTermState)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if the server accepts the upgraded connection or returns an error
|
|
||||||
_, err = reply()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
restoreTerminal(oldTermState)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the varlink sockets
|
|
||||||
reader := r.Conn.Reader
|
|
||||||
writer := r.Conn.Writer
|
|
||||||
|
|
||||||
// These are the special writers that encode input from the client.
|
|
||||||
varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin)
|
|
||||||
varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Read from the wire and direct to stdout or stderr
|
|
||||||
err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil)
|
|
||||||
defer restoreTerminal(oldTermState)
|
|
||||||
errChan <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for termResize := range resize {
|
|
||||||
b, err := json.Marshal(termResize)
|
|
||||||
if err != nil {
|
|
||||||
defer restoreTerminal(oldTermState)
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
_, err = varlinkResizeWriter.Write(b)
|
|
||||||
if err != nil {
|
|
||||||
defer restoreTerminal(oldTermState)
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Takes stdinput and sends it over the wire after being encoded
|
|
||||||
go func() {
|
|
||||||
if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil {
|
|
||||||
defer restoreTerminal(oldTermState)
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
return errChan, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach to a remote terminal
|
// Attach to a remote terminal
|
||||||
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
|
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
|
||||||
ctr, err := r.LookupContainer(c.InputArgs[0])
|
ctr, err := r.LookupContainer(c.InputArgs[0])
|
||||||
@ -796,6 +710,49 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
|
|||||||
return exitCode, finalErr
|
return exitCode, finalErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
|
||||||
|
var (
|
||||||
|
oldTermState *term.State
|
||||||
|
)
|
||||||
|
spec, err := r.Spec(cid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resize := make(chan remotecommand.TerminalSize, 5)
|
||||||
|
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
|
// Check if we are attached to a terminal. If we are, generate resize
|
||||||
|
// events, and set the terminal to raw mode
|
||||||
|
if haveTerminal && spec.Process.Terminal {
|
||||||
|
cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
|
||||||
|
logrus.SetFormatter(&RawTtyFormatter{})
|
||||||
|
term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
|
||||||
|
if err != nil {
|
||||||
|
restoreTerminal(oldTermState)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the server accepts the upgraded connection or returns an error
|
||||||
|
_, err = reply()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
restoreTerminal(oldTermState)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, nil)
|
||||||
|
return errChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PauseContainers pauses container(s) based on CLI inputs.
|
// PauseContainers pauses container(s) based on CLI inputs.
|
||||||
func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) {
|
func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) {
|
||||||
var (
|
var (
|
||||||
@ -1037,8 +994,11 @@ func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, co
|
|||||||
|
|
||||||
// ExecContainer executes a command in the container
|
// ExecContainer executes a command in the container
|
||||||
func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
|
func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
|
||||||
|
var (
|
||||||
|
oldTermState *term.State
|
||||||
|
ec int = define.ExecErrorCodeGeneric
|
||||||
|
)
|
||||||
// default invalid command exit code
|
// default invalid command exit code
|
||||||
ec := 125
|
|
||||||
// Validate given environment variables
|
// Validate given environment variables
|
||||||
env := map[string]string{}
|
env := map[string]string{}
|
||||||
if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil {
|
if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil {
|
||||||
@ -1051,6 +1011,23 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal
|
|||||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resize := make(chan remotecommand.TerminalSize, 5)
|
||||||
|
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
|
// Check if we are attached to a terminal. If we are, generate resize
|
||||||
|
// events, and set the terminal to raw mode
|
||||||
|
if haveTerminal && cli.Tty {
|
||||||
|
cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
|
||||||
|
if err != nil {
|
||||||
|
return ec, err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
|
||||||
|
logrus.SetFormatter(&RawTtyFormatter{})
|
||||||
|
term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
opts := iopodman.ExecOpts{
|
opts := iopodman.ExecOpts{
|
||||||
Name: cli.InputArgs[0],
|
Name: cli.InputArgs[0],
|
||||||
Tty: cli.Tty,
|
Tty: cli.Tty,
|
||||||
@ -1059,18 +1036,79 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal
|
|||||||
User: &cli.User,
|
User: &cli.User,
|
||||||
Workdir: &cli.Workdir,
|
Workdir: &cli.Workdir,
|
||||||
Env: &envs,
|
Env: &envs,
|
||||||
|
DetachKeys: &cli.DetachKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
receive, err := iopodman.ExecContainer().Send(r.Conn, varlink.Upgrade, opts)
|
inputStream := os.Stdin
|
||||||
|
if !cli.Interactive {
|
||||||
|
inputStream = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := iopodman.ExecContainer().Send(r.Conn, varlink.Upgrade, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ec, errors.Wrapf(err, "Exec failed to contact service for %s", cli.InputArgs)
|
return ec, errors.Wrapf(err, "Exec failed to contact service for %s", cli.InputArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = receive()
|
_, err = reply()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ec, errors.Wrapf(err, "Exec operation failed for %s", cli.InputArgs)
|
return ec, errors.Wrapf(err, "Exec operation failed for %s", cli.InputArgs)
|
||||||
}
|
}
|
||||||
|
ecChan := make(chan int, 1)
|
||||||
|
errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, inputStream, os.Stdout, oldTermState, resize, ecChan)
|
||||||
|
|
||||||
// TODO return exit code from exec call
|
ec = <-ecChan
|
||||||
return 0, nil
|
err = <-errChan
|
||||||
|
|
||||||
|
return ec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, stdin *os.File, stdout *os.File, oldTermState *term.State, resize chan remotecommand.TerminalSize, ecChan chan int) chan error {
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
// These are the special writers that encode input from the client.
|
||||||
|
varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin)
|
||||||
|
varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Read from the wire and direct to stdout or stderr
|
||||||
|
err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil, ecChan)
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
sendGenericError(ecChan)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for termResize := range resize {
|
||||||
|
b, err := json.Marshal(termResize)
|
||||||
|
if err != nil {
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
sendGenericError(ecChan)
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
_, err = varlinkResizeWriter.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
sendGenericError(ecChan)
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if stdin != nil {
|
||||||
|
// Takes stdinput and sends it over the wire after being encoded
|
||||||
|
go func() {
|
||||||
|
if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil {
|
||||||
|
defer restoreTerminal(oldTermState)
|
||||||
|
sendGenericError(ecChan)
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendGenericError(ecChan chan int) {
|
||||||
|
if ecChan != nil {
|
||||||
|
ecChan <- define.ExecErrorCodeGeneric
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"k8s.io/client-go/tools/remotecommand"
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
)
|
)
|
||||||
@ -76,3 +77,25 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||||||
|
|
||||||
return bytes, err
|
return bytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) {
|
||||||
|
logrus.Debugf("Handling terminal attach")
|
||||||
|
|
||||||
|
subCtx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
resizeTty(subCtx, resize)
|
||||||
|
|
||||||
|
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
// allow caller to not have to do any cleaning up if we error here
|
||||||
|
cancel()
|
||||||
|
return nil, nil, errors.Wrapf(err, "unable to save terminal state")
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.SetFormatter(&RawTtyFormatter{})
|
||||||
|
if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil {
|
||||||
|
return cancel, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cancel, oldTermState, nil
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
@ -108,25 +107,3 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) {
|
|
||||||
logrus.Debugf("Handling terminal attach")
|
|
||||||
|
|
||||||
subCtx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
resizeTty(subCtx, resize)
|
|
||||||
|
|
||||||
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
|
||||||
if err != nil {
|
|
||||||
// allow caller to not have to do any cleaning up if we error here
|
|
||||||
cancel()
|
|
||||||
return nil, nil, errors.Wrapf(err, "unable to save terminal state")
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.SetFormatter(&RawTtyFormatter{})
|
|
||||||
if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil {
|
|
||||||
return cancel, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cancel, oldTermState, nil
|
|
||||||
}
|
|
||||||
|
@ -68,7 +68,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st
|
|||||||
reader, writer, _, pw, streams := setupStreams(call)
|
reader, writer, _, pw, streams := setupStreams(call)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil {
|
if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -83,7 +83,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st
|
|||||||
logrus.Error(finalErr)
|
logrus.Error(finalErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = virtwriter.HangUp(writer); err != nil {
|
if err = virtwriter.HangUp(writer, 0); err != nil {
|
||||||
logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error())
|
logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error())
|
||||||
}
|
}
|
||||||
return call.Writer.Flush()
|
return call.Writer.Flush()
|
||||||
|
@ -782,6 +782,9 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO
|
|||||||
fmt.Sprintf("exec requires a running container, %s is %s", ctr.ID(), state.String()))
|
fmt.Sprintf("exec requires a running container, %s is %s", ctr.ID(), state.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACK the client upgrade request
|
||||||
|
call.ReplyExecContainer()
|
||||||
|
|
||||||
envs := []string{}
|
envs := []string{}
|
||||||
if opts.Env != nil {
|
if opts.Env != nil {
|
||||||
envs = *opts.Env
|
envs = *opts.Env
|
||||||
@ -797,44 +800,52 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO
|
|||||||
workDir = *opts.Workdir
|
workDir = *opts.Workdir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var detachKeys string
|
||||||
|
if opts.DetachKeys != nil {
|
||||||
|
detachKeys = *opts.DetachKeys
|
||||||
|
}
|
||||||
|
|
||||||
resizeChan := make(chan remotecommand.TerminalSize)
|
resizeChan := make(chan remotecommand.TerminalSize)
|
||||||
errChan := make(chan error)
|
|
||||||
|
|
||||||
reader, writer, _, pipeWriter, streams := setupStreams(call)
|
reader, writer, _, pipeWriter, streams := setupStreams(call)
|
||||||
|
|
||||||
|
type ExitCodeError struct {
|
||||||
|
ExitCode uint32
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
ecErrChan := make(chan ExitCodeError, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
fmt.Printf("ExecContainer Start Reader\n")
|
if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan, nil); err != nil {
|
||||||
if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan); err != nil {
|
ecErrChan <- ExitCodeError{
|
||||||
fmt.Printf("ExecContainer Reader err %s, %s\n", err.Error(), errors.Cause(err).Error())
|
define.ExecErrorCodeGeneric,
|
||||||
errChan <- err
|
err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Debugging...
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
fmt.Printf("ExecContainer Start ctr.Exec\n")
|
ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, detachKeys)
|
||||||
// TODO detach keys and resize
|
|
||||||
// TODO add handling for exit code
|
|
||||||
// TODO capture exit code and return to main thread
|
|
||||||
_, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, nil, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ExecContainer Exec err %s, %s\n", err.Error(), errors.Cause(err).Error())
|
logrus.Errorf(err.Error())
|
||||||
errChan <- errors.Wrapf(err, "ExecContainer failed for container %s", ctr.ID())
|
}
|
||||||
|
ecErrChan <- ExitCodeError{
|
||||||
|
uint32(ec),
|
||||||
|
err,
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
execErr := <-errChan
|
ecErr := <-ecErrChan
|
||||||
|
|
||||||
if execErr != nil && errors.Cause(execErr) != io.EOF {
|
exitCode := define.TranslateExecErrorToExitCode(int(ecErr.ExitCode), ecErr.Error)
|
||||||
fmt.Printf("ExecContainer err: %s\n", execErr.Error())
|
|
||||||
return call.ReplyErrorOccurred(execErr.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = virtwriter.HangUp(writer); err != nil {
|
if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil {
|
||||||
fmt.Printf("ExecContainer hangup err: %s\n", err.Error())
|
|
||||||
logrus.Errorf("ExecContainer failed to HANG-UP on %s: %s", ctr.ID(), err.Error())
|
logrus.Errorf("ExecContainer failed to HANG-UP on %s: %s", ctr.ID(), err.Error())
|
||||||
}
|
}
|
||||||
return call.Writer.Flush()
|
|
||||||
|
if err := call.Writer.Flush(); err != nil {
|
||||||
|
logrus.Errorf("Exec Container err: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ecErr.Error
|
||||||
}
|
}
|
||||||
|
@ -89,10 +89,14 @@ func (v VirtWriteCloser) Write(input []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reader decodes the content that comes over the wire and directs it to the proper destination.
|
// Reader decodes the content that comes over the wire and directs it to the proper destination.
|
||||||
func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer, resize chan remotecommand.TerminalSize) error {
|
func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remotecommand.TerminalSize, execEcChan chan int) error {
|
||||||
var messageSize int64
|
var messageSize int64
|
||||||
headerBytes := make([]byte, 8)
|
headerBytes := make([]byte, 8)
|
||||||
|
|
||||||
|
if r == nil {
|
||||||
|
return errors.Errorf("Reader must not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := io.ReadFull(r, headerBytes)
|
n, err := io.ReadFull(r, headerBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,21 +110,28 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer
|
|||||||
|
|
||||||
switch IntToSocketDest(int(headerBytes[0])) {
|
switch IntToSocketDest(int(headerBytes[0])) {
|
||||||
case ToStdout:
|
case ToStdout:
|
||||||
|
if output != nil {
|
||||||
_, err := io.CopyN(output, r, messageSize)
|
_, err := io.CopyN(output, r, messageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case ToStderr:
|
case ToStderr:
|
||||||
|
if errput != nil {
|
||||||
_, err := io.CopyN(errput, r, messageSize)
|
_, err := io.CopyN(errput, r, messageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case ToStdin:
|
case ToStdin:
|
||||||
|
if input != nil {
|
||||||
_, err := io.CopyN(input, r, messageSize)
|
_, err := io.CopyN(input, r, messageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case TerminalResize:
|
case TerminalResize:
|
||||||
|
if resize != nil {
|
||||||
out := make([]byte, messageSize)
|
out := make([]byte, messageSize)
|
||||||
if messageSize > 0 {
|
if messageSize > 0 {
|
||||||
_, err = io.ReadFull(r, out)
|
_, err = io.ReadFull(r, out)
|
||||||
@ -135,6 +146,7 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resize <- resizeEvent
|
resize <- resizeEvent
|
||||||
|
}
|
||||||
case Quit:
|
case Quit:
|
||||||
out := make([]byte, messageSize)
|
out := make([]byte, messageSize)
|
||||||
if messageSize > 0 {
|
if messageSize > 0 {
|
||||||
@ -144,6 +156,10 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if execEcChan != nil {
|
||||||
|
ecInt := binary.BigEndian.Uint32(out)
|
||||||
|
execEcChan <- int(ecInt)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -154,9 +170,11 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HangUp sends message to peer to close connection
|
// HangUp sends message to peer to close connection
|
||||||
func HangUp(writer *bufio.Writer) (err error) {
|
func HangUp(writer *bufio.Writer, ec uint32) (err error) {
|
||||||
n := 0
|
n := 0
|
||||||
msg := []byte("HANG-UP")
|
msg := make([]byte, 4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(msg, ec)
|
||||||
|
|
||||||
writeQuit := NewVirtWriteCloser(writer, Quit)
|
writeQuit := NewVirtWriteCloser(writer, Quit)
|
||||||
if n, err = writeQuit.Write(msg); err != nil {
|
if n, err = writeQuit.Write(msg); err != nil {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build !remoteclient
|
|
||||||
|
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -67,6 +65,8 @@ var _ = Describe("Podman exec", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("podman exec simple command using latest", func() {
|
It("podman exec simple command using latest", func() {
|
||||||
|
// the remote client doesn't use latest
|
||||||
|
SkipIfRemote()
|
||||||
setup := podmanTest.RunTopContainer("test1")
|
setup := podmanTest.RunTopContainer("test1")
|
||||||
setup.WaitWithDefaultTimeout()
|
setup.WaitWithDefaultTimeout()
|
||||||
Expect(setup.ExitCode()).To(Equal(0))
|
Expect(setup.ExitCode()).To(Equal(0))
|
||||||
@ -81,27 +81,35 @@ var _ = Describe("Podman exec", func() {
|
|||||||
setup.WaitWithDefaultTimeout()
|
setup.WaitWithDefaultTimeout()
|
||||||
Expect(setup.ExitCode()).To(Equal(0))
|
Expect(setup.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
session := podmanTest.Podman([]string{"exec", "-l", "--env", "FOO=BAR", "printenv", "FOO"})
|
session := podmanTest.Podman([]string{"exec", "--env", "FOO=BAR", "test1", "printenv", "FOO"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
match, _ := session.GrepString("BAR")
|
match, _ := session.GrepString("BAR")
|
||||||
Expect(match).Should(BeTrue())
|
Expect(match).Should(BeTrue())
|
||||||
|
|
||||||
session = podmanTest.Podman([]string{"exec", "-l", "--env", "PATH=/bin", "printenv", "PATH"})
|
session = podmanTest.Podman([]string{"exec", "--env", "PATH=/bin", "test1", "printenv", "PATH"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
match, _ = session.GrepString("/bin")
|
match, _ = session.GrepString("/bin")
|
||||||
Expect(match).Should(BeTrue())
|
Expect(match).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman exec os.Setenv env", func() {
|
||||||
|
// remote doesn't properly interpret os.Setenv
|
||||||
|
SkipIfRemote()
|
||||||
|
setup := podmanTest.RunTopContainer("test1")
|
||||||
|
setup.WaitWithDefaultTimeout()
|
||||||
|
Expect(setup.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
os.Setenv("FOO", "BAR")
|
os.Setenv("FOO", "BAR")
|
||||||
session = podmanTest.Podman([]string{"exec", "-l", "--env", "FOO", "printenv", "FOO"})
|
session := podmanTest.Podman([]string{"exec", "--env", "FOO", "test1", "printenv", "FOO"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
match, _ = session.GrepString("BAR")
|
match, _ := session.GrepString("BAR")
|
||||||
Expect(match).Should(BeTrue())
|
Expect(match).Should(BeTrue())
|
||||||
os.Unsetenv("FOO")
|
os.Unsetenv("FOO")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman exec exit code", func() {
|
It("podman exec exit code", func() {
|
||||||
setup := podmanTest.RunTopContainer("test1")
|
setup := podmanTest.RunTopContainer("test1")
|
||||||
setup.WaitWithDefaultTimeout()
|
setup.WaitWithDefaultTimeout()
|
||||||
@ -143,13 +151,13 @@ var _ = Describe("Podman exec", func() {
|
|||||||
setup.WaitWithDefaultTimeout()
|
setup.WaitWithDefaultTimeout()
|
||||||
Expect(setup.ExitCode()).To(Equal(0))
|
Expect(setup.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/tmp", "pwd"})
|
session := podmanTest.Podman([]string{"exec", "--workdir", "/tmp", "test1", "pwd"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
match, _ := session.GrepString("/tmp")
|
match, _ := session.GrepString("/tmp")
|
||||||
Expect(match).Should(BeTrue())
|
Expect(match).Should(BeTrue())
|
||||||
|
|
||||||
session = podmanTest.Podman([]string{"exec", "-l", "-w", "/tmp", "pwd"})
|
session = podmanTest.Podman([]string{"exec", "-w", "/tmp", "test1", "pwd"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
match, _ = session.GrepString("/tmp")
|
match, _ = session.GrepString("/tmp")
|
||||||
@ -161,11 +169,11 @@ var _ = Describe("Podman exec", func() {
|
|||||||
setup.WaitWithDefaultTimeout()
|
setup.WaitWithDefaultTimeout()
|
||||||
Expect(setup.ExitCode()).To(Equal(0))
|
Expect(setup.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/missing", "pwd"})
|
session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(1))
|
Expect(session.ExitCode()).To(Equal(1))
|
||||||
|
|
||||||
session = podmanTest.Podman([]string{"exec", "-l", "-w", "/missing", "pwd"})
|
session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(1))
|
Expect(session.ExitCode()).To(Equal(1))
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user