kpod exec

Initial wiring of kpod exec. We wont support the following options
for exec:

* detach -- unsure of use case
* detach-keys -- not supported by runc
* interactive -- all terminals will be interactive

Not adding exec tests as we need to think about how to support a
test that requires console access but our CI tests have no console.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2017-11-28 16:25:01 -06:00
parent 8d31ec2ad7
commit e8a32e3722
10 changed files with 334 additions and 3 deletions

View File

@ -37,6 +37,7 @@ libpod is currently in active development.
| [kpod-attach(1)](/docs/kpod-attach.1.md) | Attach to a running container.
| [kpod-cp(1)](/docs/kpod-cp.1.md) | Instead of providing a `kpod cp` command, the man page `kpod-cp` describes how to use the `kpod mount` command to have even more flexibility and functionality.||
| [kpod-diff(1)](/docs/kpod-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
| [kpod-exec(1)](/docs/kpod-exec.1.md) | Execute a command in a running container.
| [kpod-export(1)](/docs/kpod-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)|
| [kpod-history(1)](/docs/kpod-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)|
| [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)|

86
cmd/kpod/exec.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod"
"github.com/urfave/cli"
)
var (
execFlags = []cli.Flag{
cli.StringSliceFlag{
Name: "env, e",
Usage: "Set environment variables",
},
cli.BoolFlag{
Name: "privileged",
Usage: "Give the process extended Linux capabilities inside the container. The default is false",
},
cli.BoolFlag{
Name: "tty, t",
Usage: "Allocate a pseudo-TTY. The default is false",
},
cli.StringFlag{
Name: "user, u",
Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command",
},
}
execDescription = `
kpod exec
Run a command in a running container
`
execCommand = cli.Command{
Name: "exec",
Usage: "Run a process in a running container",
Description: execDescription,
Flags: execFlags,
Action: execCmd,
ArgsUsage: "CONTAINER-NAME",
}
)
func execCmd(c *cli.Context) error {
var envs []string
args := c.Args()
if len(args) < 1 {
return errors.Errorf("you must provide one container name or id")
}
if len(args) < 2 {
return errors.Errorf("you must provide a command to exec")
}
cmd := args[1:]
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
ctr, err := runtime.LookupContainer(args[0])
if err != nil {
return errors.Wrapf(err, "unable to exec into %s", args[0])
}
// Create a list of keys provided by the user
var userEnvKeys []string
for _, env := range c.StringSlice("env") {
splitEnv := strings.Split(env, "=")
userEnvKeys = append(userEnvKeys, splitEnv[0])
}
envs = append(envs, c.StringSlice("env")...)
// if the default key isnt in the user-provided list, add the default
// key and value to the environment variables. this is needed to set
// PATH for example.
for k, v := range defaultEnvVariables {
if !libpod.StringInSlice(k, userEnvKeys) {
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
}
}
return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"))
}

View File

@ -37,6 +37,7 @@ func main() {
attachCommand,
createCommand,
diffCommand,
execCommand,
exportCommand,
historyCommand,
imagesCommand,

View File

@ -682,6 +682,21 @@ _kpod_diff() {
esac
}
_kpod_exec() {
local options_with_args="
-e
--env
--user
-u
"
local boolean_options="
--privileged
--tty
-t
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_export() {
local options_with_args="
--output
@ -1437,6 +1452,7 @@ _kpod_kpod() {
attach
create
diff
exec
export
history
images

43
docs/kpod-exec.1.md Normal file
View File

@ -0,0 +1,43 @@
% kpod(1) kpod-exec - Execute a command in a running container
% Brent Baude
# kpod-exec "1" "December 2017" "kpod"
## NAME
kpod-exec - Execute a command in a running container
## SYNOPSIS
**kpod exec**
**CONTAINER**
[COMMAND] [ARG...]
[**--help**|**-h**]
## DESCRIPTION
**kpod exec** executes a command in a running container.
## OPTIONS
**--env, e**
You may specify arbitrary environment variables that are available for the
command to be executed.
**--interactive, -i**
Not supported. All exec commands are interactive by default.
**--privileged**
Give the process extended Linux capabilities when running the command in container.
**--tty, -t**
Allocate a pseudo-TTY.
**--user, -u**
Sets the username or UID used and optionally the groupname or GID for the specified command.
The following examples are all valid:
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
## EXAMPLES
## SEE ALSO
kpod(1), kpod-run(1)
## HISTORY
December 2017, Originally compiled by Brent Baude<bbaude@redhat.com>

View File

@ -61,6 +61,9 @@ create a new container
### diff
Inspect changes on a container or image's filesystem
## exec
Execute a command in a running container.
### export
Export container's filesystem contents as a tar archive

View File

@ -14,6 +14,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/daemon/caps"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/stringid"
@ -552,9 +553,36 @@ func (c *Container) Kill(signal uint) error {
}
// Exec starts a new process inside the container
// Returns fully qualified URL of streaming server for executed process
func (c *Container) Exec(cmd []string, tty bool, stdin bool) (string, error) {
return "", ErrNotImplemented
func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) error {
var capList []string
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return err
}
conState := c.state.State
if conState != ContainerStateRunning {
return errors.Errorf("cannot attach to container that is not running")
}
if privileged {
capList = caps.GetAllCapabilities()
}
globalOpts := runcGlobalOptions{
log: c.LogPath(),
}
execOpts := runcExecOptions{
capAdd: capList,
pidFile: filepath.Join(c.state.RunDir, fmt.Sprintf("%s-execpid", stringid.GenerateNonCryptoID()[:12])),
env: env,
user: user,
cwd: c.config.Spec.Process.Cwd,
tty: tty,
}
return c.runtime.ociRuntime.execContainer(c, cmd, globalOpts, execOpts)
}
// Attach attaches to a container

View File

@ -454,3 +454,9 @@ func (r *OCIRuntime) pauseContainer(ctr *Container) error {
func (r *OCIRuntime) unpauseContainer(ctr *Container) error {
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "resume", ctr.ID())
}
//execContiner executes a command in a running container
func (r *OCIRuntime) execContainer(c *Container, cmd []string, globalOpts runcGlobalOptions, commandOpts runcExecOptions) error {
r.RuncExec(c, cmd, globalOpts, commandOpts)
return nil
}

117
libpod/runc.go Normal file
View File

@ -0,0 +1,117 @@
package libpod
import (
"os"
"os/exec"
"strings"
"github.com/sirupsen/logrus"
)
type runcGlobalOptions struct {
log string
logFormat string
root string
criu string
systemdCgroup bool
}
type runcExecOptions struct {
consoleSocket string
cwd string
env []string
tty bool
user string
processPath string
detach bool
pidFile string
processLabel string
apparmor string
noNewPrivs bool
capAdd []string
}
func parseGlobalOptionsToArgs(opts runcGlobalOptions) []string {
args := []string{}
if opts.log != "" {
args = append(args, "--log", opts.log)
}
if opts.logFormat != "" {
args = append(args, "--log-format", opts.logFormat)
}
if opts.root != "" {
args = append(args, "--root", opts.root)
}
if opts.criu != "" {
args = append(args, "--criu", opts.criu)
}
if opts.systemdCgroup {
args = append(args, "--systemd-cgroup")
}
return args
}
// RuncExec executes 'runc --options exec --options cmd'
func (r *OCIRuntime) RuncExec(container *Container, command []string, globalOpts runcGlobalOptions, execOpts runcExecOptions) error {
args := []string{}
args = append(args, parseGlobalOptionsToArgs(globalOpts)...)
// Add subcommand
args = append(args, "exec")
// Now add subcommand args
if execOpts.consoleSocket != "" {
args = append(args, "--console-socket", execOpts.consoleSocket)
}
if execOpts.cwd != "" {
args = append(args, "--cwd", execOpts.cwd)
}
if len(execOpts.env) > 0 {
for _, envInput := range execOpts.env {
args = append(args, "--env", envInput)
}
}
if execOpts.tty {
args = append(args, "--tty")
}
if execOpts.user != "" {
args = append(args, "--user", execOpts.user)
}
if execOpts.processPath != "" {
args = append(args, "--process", execOpts.processPath)
}
if execOpts.detach {
args = append(args, "--detach")
}
if execOpts.pidFile != "" {
args = append(args, "--pid-file", execOpts.pidFile)
}
if execOpts.processLabel != "" {
args = append(args, "--process-label", execOpts.processLabel)
}
if execOpts.apparmor != "" {
args = append(args, "--apparmor", execOpts.apparmor)
}
if execOpts.noNewPrivs {
args = append(args, "--no-new-privs")
}
if len(execOpts.capAdd) > 0 {
for _, capAddValue := range execOpts.capAdd {
args = append(args, "--cap", capAddValue)
}
}
// Append Cid
args = append(args, container.ID())
// Append Cmd
args = append(args, command...)
logrus.Debug("Executing runc command: %s %s", r.path, strings.Join(args, " "))
cmd := exec.Command(r.path, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Start()
err := cmd.Wait()
return err
}

30
test/kpod_exec.bats Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bats
load helpers
function teardown() {
cleanup_test
}
function setup() {
copy_images
}
@test "exec into a bogus container" {
run ${KPOD_BINARY} ${KPOD_OPTIONS} exec foobar ls
echo "$output"
[ "$status" -eq 1 ]
}
@test "exec without command should fail" {
run ${KPOD_BINARY} ${KPOD_OPTIONS} exec foobar
echo "$output"
[ "$status" -eq 1 ]
}
@test "exec simple command" {
${KPOD_BINARY} ${KPOD_OPTIONS} run -d -t --name foobar1 ${ALPINE} sleep 60
run ${KPOD_BINARY} ${KPOD_OPTIONS} exec foobar1 ls
echo "$output"
[ "$status" -eq 0 ]
}