Add CLI frontend for detached exec

Add a new ContainerEngine method for creating a detached exec
session, and wire in the frontend code to do this. As part of
this, move Streams out of ExecOptions to the function signature
in an effort to share the struct between both methods.

Fixes #5884

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:
Matthew Heon 2020-05-18 17:27:27 -04:00
parent 43413887c0
commit 05a034118f
6 changed files with 89 additions and 28 deletions

View File

@ -2,9 +2,11 @@ package containers
import (
"bufio"
"fmt"
"os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
envLib "github.com/containers/libpod/pkg/env"
"github.com/pkg/errors"
@ -41,10 +43,12 @@ var (
var (
envInput, envFile []string
execOpts entities.ExecOptions
execDetach bool
)
func execFlags(flags *pflag.FlagSet) {
flags.SetInterspersed(false)
flags.BoolVarP(&execDetach, "detach", "d", false, "Run the exec session in detached mode (backgrounded)")
flags.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _")
flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables")
flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables")
@ -106,16 +110,27 @@ func exec(cmd *cobra.Command, args []string) error {
}
execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv)
execOpts.Streams.OutputStream = os.Stdout
execOpts.Streams.ErrorStream = os.Stderr
if execOpts.Interactive {
execOpts.Streams.InputStream = bufio.NewReader(os.Stdin)
execOpts.Streams.AttachInput = true
}
execOpts.Streams.AttachOutput = true
execOpts.Streams.AttachError = true
exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts)
if !execDetach {
streams := define.AttachStreams{}
streams.OutputStream = os.Stdout
streams.ErrorStream = os.Stderr
if execOpts.Interactive {
streams.InputStream = bufio.NewReader(os.Stdin)
streams.AttachInput = true
}
streams.AttachOutput = true
streams.AttachError = true
exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts, streams)
registry.SetExitCode(exitCode)
return err
}
id, err := registry.ContainerEngine().ContainerExecDetached(registry.GetContext(), nameOrId, execOpts)
if err != nil {
return err
}
fmt.Println(id)
return nil
}

View File

@ -242,7 +242,6 @@ type ExecOptions struct {
Latest bool
PreserveFDs uint
Privileged bool
Streams define.AttachStreams
Tty bool
User string
WorkDir string

View File

@ -19,7 +19,8 @@ type ContainerEngine interface {
ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error)
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error)
ContainerExec(ctx context.Context, nameOrId string, options ExecOptions, streams define.AttachStreams) (int, error)
ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error)

View File

@ -536,7 +536,22 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string,
return nil
}
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) {
func makeExecConfig(options entities.ExecOptions) *libpod.ExecConfig {
execConfig := new(libpod.ExecConfig)
execConfig.Command = options.Cmd
execConfig.Terminal = options.Tty
execConfig.Privileged = options.Privileged
execConfig.Environment = options.Envs
execConfig.User = options.User
execConfig.WorkDir = options.WorkDir
execConfig.DetachKeys = &options.DetachKeys
execConfig.PreserveFDs = options.PreserveFDs
execConfig.AttachStdin = options.Interactive
return execConfig
}
func checkExecPreserveFDs(options entities.ExecOptions) (int, error) {
ec := define.ExecErrorCodeGeneric
if options.PreserveFDs > 0 {
entries, err := ioutil.ReadDir("/proc/self/fd")
@ -559,15 +574,52 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o
}
}
}
return ec, nil
}
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) {
ec, err := checkExecPreserveFDs(options)
if err != nil {
return ec, err
}
ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod)
if err != nil {
return ec, err
}
ctr := ctrs[0]
ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys)
execConfig := makeExecConfig(options)
ec, err = terminal.ExecAttachCtr(ctx, ctr, execConfig, &streams)
return define.TranslateExecErrorToExitCode(ec, err), err
}
func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrId string, options entities.ExecOptions) (string, error) {
_, err := checkExecPreserveFDs(options)
if err != nil {
return "", err
}
ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod)
if err != nil {
return "", err
}
ctr := ctrs[0]
execConfig := makeExecConfig(options)
// Create and start the exec session
id, err := ctr.ExecCreate(execConfig)
if err != nil {
return "", err
}
// TODO: we should try and retrieve exit code if this fails.
if err := ctr.ExecStart(id); err != nil {
return "", err
}
return id, nil
}
func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) {
var reports []*entities.ContainerStartReport
var exitCode = define.ExecErrorCodeGeneric

View File

@ -15,13 +15,13 @@ import (
)
// ExecAttachCtr execs and attaches to a container
func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) {
func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (int, error) {
resize := make(chan remotecommand.TerminalSize)
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 && tty {
if haveTerminal && execConfig.Terminal {
cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
if err != nil {
return -1, err
@ -34,16 +34,6 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged b
}()
}
execConfig := new(libpod.ExecConfig)
execConfig.Command = cmd
execConfig.Terminal = tty
execConfig.Privileged = privileged
execConfig.Environment = env
execConfig.User = user
execConfig.WorkDir = workDir
execConfig.DetachKeys = &detachKeys
execConfig.PreserveFDs = preserveFDs
return ctr.Exec(execConfig, streams, resize)
}

View File

@ -329,10 +329,14 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string,
return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr)
}
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) {
func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) {
return 125, errors.New("not implemented")
}
func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) {
return "", errors.New("not implemented")
}
func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint
attachErr := make(chan error)
go func() {