summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--cmd/kpod/exec.go86
-rw-r--r--cmd/kpod/main.go1
-rw-r--r--completions/bash/kpod16
-rw-r--r--docs/kpod-exec.1.md43
-rw-r--r--docs/kpod.1.md3
-rw-r--r--libpod/container.go34
-rw-r--r--libpod/oci.go6
-rw-r--r--libpod/runc.go117
-rw-r--r--test/kpod_exec.bats30
10 files changed, 334 insertions, 3 deletions
diff --git a/README.md b/README.md
index 049dbf00a..967682cb3 100644
--- a/README.md
+++ b/README.md
@@ -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)|
diff --git a/cmd/kpod/exec.go b/cmd/kpod/exec.go
new file mode 100644
index 000000000..f76983810
--- /dev/null
+++ b/cmd/kpod/exec.go
@@ -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"))
+}
diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go
index 7382644b9..708031dc9 100644
--- a/cmd/kpod/main.go
+++ b/cmd/kpod/main.go
@@ -37,6 +37,7 @@ func main() {
attachCommand,
createCommand,
diffCommand,
+ execCommand,
exportCommand,
historyCommand,
imagesCommand,
diff --git a/completions/bash/kpod b/completions/bash/kpod
index a5da95a3e..6c66c63c0 100644
--- a/completions/bash/kpod
+++ b/completions/bash/kpod
@@ -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
diff --git a/docs/kpod-exec.1.md b/docs/kpod-exec.1.md
new file mode 100644
index 000000000..1de5da39b
--- /dev/null
+++ b/docs/kpod-exec.1.md
@@ -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>
diff --git a/docs/kpod.1.md b/docs/kpod.1.md
index 7009289c7..dec29e395 100644
--- a/docs/kpod.1.md
+++ b/docs/kpod.1.md
@@ -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
diff --git a/libpod/container.go b/libpod/container.go
index 40aa689ff..474d4be95 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -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
diff --git a/libpod/oci.go b/libpod/oci.go
index 6ea6bb64f..323e97273 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -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
+}
diff --git a/libpod/runc.go b/libpod/runc.go
new file mode 100644
index 000000000..efbb51b24
--- /dev/null
+++ b/libpod/runc.go
@@ -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
+}
diff --git a/test/kpod_exec.bats b/test/kpod_exec.bats
new file mode 100644
index 000000000..d495b1687
--- /dev/null
+++ b/test/kpod_exec.bats
@@ -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 ]
+}