diff --git a/.travis.yml b/.travis.yml index 9ebe8b0c..be326c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ services: before_install: - sudo apt-get -qq update - sudo apt-get -qq install btrfs-tools libdevmapper-dev libgpgme11-dev libapparmor-dev libseccomp-dev - - sudo apt-get -qq install autoconf automake bison e2fslibs-dev libfuse-dev libtool liblzma-dev + - sudo apt-get -qq install autoconf automake bison e2fslibs-dev libfuse-dev libtool liblzma-dev gettext install: - make install.tools diff --git a/Dockerfile b/Dockerfile index a747e342..3caf5ac5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \ curl \ e2fslibs-dev \ gawk \ + gettext \ iptables \ pkg-config \ libaio-dev \ diff --git a/README.md b/README.md index 3453a3b1..322c51d4 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-umount(1)](/docs/kpod-umount.1.md) | Unmount a working container's root filesystem || | [kpod-unpause(1)](/docs/kpod-unpause.1.md) | Unpause one or more running containers || | [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| +| [kpod-wait(1)](/docs/kpod-wait.1.md) | Wait on one or more containers to stop and print their exit codes|| ## Configuration | File | Description | diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 75c32d6b..c00de39b 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -54,6 +54,7 @@ func main() { umountCommand, unpauseCommand, versionCommand, + waitCommand, } app.Before = func(c *cli.Context) error { logrus.SetLevel(logrus.ErrorLevel) diff --git a/cmd/kpod/wait.go b/cmd/kpod/wait.go new file mode 100644 index 00000000..b166e330 --- /dev/null +++ b/cmd/kpod/wait.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "os" + + "github.com/kubernetes-incubator/cri-o/libkpod" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + waitDescription = ` + kpod wait + + Block until one or more containers stop and then print their exit codes +` + + waitCommand = cli.Command{ + Name: "wait", + Usage: "Block on one or more containers", + Description: waitDescription, + Action: waitCmd, + ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + } +) + +func waitCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("you must provide at least one container name or id") + } + + config, err := getConfig(c) + if err != nil { + return errors.Wrapf(err, "could not get config") + } + server, err := libkpod.New(config) + if err != nil { + return errors.Wrapf(err, "could not get container server") + } + defer server.Shutdown() + err = server.Update() + if err != nil { + return errors.Wrapf(err, "could not update list of containers") + } + + var lastError error + for _, container := range c.Args() { + returnCode, err := server.ContainerWait(container) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to wait for the container %v", container) + } else { + fmt.Println(returnCode) + } + } + + return lastError +} diff --git a/completions/bash/kpod b/completions/bash/kpod index e6a95519..4803864d 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -388,6 +388,11 @@ _kpod_unpause() { " local boolean_options="" _complete_ "$options_with_args" "$boolean_options" + +_kpod_wait() { + local options_with_args="" + local boolean_options="--help -h" + _complete_ "$options_with_args" "$boolean_options" } _complete_() { @@ -455,6 +460,7 @@ _kpod_kpod() { unmount unpause version + wait " case "$prev" in diff --git a/docs/kpod-wait.1.md b/docs/kpod-wait.1.md new file mode 100644 index 00000000..290cdedf --- /dev/null +++ b/docs/kpod-wait.1.md @@ -0,0 +1,36 @@ +% kpod(1) kpod-wait - Waits on a container +% Brent Baude +# kpod-wait "1" "September 2017" "kpod" + +## NAME +kpod wait - Waits on one or more containers to stop and prints exit code + +## SYNOPSIS +**kpod wait** +[**--help**|**-h**] + +## DESCRIPTION +Waits on one or more containers to stop. The container can be referred to by its +name or ID. In the case of multiple containers, kpod will wait on each consecutively. +After the container stops, the container's return code is printed. + +**kpod [GLOBAL OPTIONS] wait ** + +## GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +## EXAMPLES + + kpod wait mywebserver + + kpod wait 860a4b23 + + kpod wait mywebserver myftpserver + +## SEE ALSO +kpod(1), crio(8), crio.conf(5) + +## HISTORY +September 2017, Originally compiled by Brent Baude diff --git a/libkpod/wait.go b/libkpod/wait.go new file mode 100644 index 00000000..c7ba5732 --- /dev/null +++ b/libkpod/wait.go @@ -0,0 +1,42 @@ +package libkpod + +import ( + "github.com/kubernetes-incubator/cri-o/oci" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/wait" +) + +func isStopped(c *ContainerServer, ctr *oci.Container) bool { + c.runtime.UpdateStatus(ctr) + cStatus := c.runtime.ContainerStatus(ctr) + if cStatus.Status == oci.ContainerStateStopped { + return true + } + return false +} + +// ContainerWait stops a running container with a grace period (i.e., timeout). +func (c *ContainerServer) ContainerWait(container string) (int32, error) { + ctr, err := c.LookupContainer(container) + if err != nil { + return 0, errors.Wrapf(err, "failed to find container %s", container) + } + + err = wait.PollImmediateInfinite(1, + func() (bool, error) { + if !isStopped(c, ctr) { + return false, nil + } else { // nolint + return true, nil // nolint + } // nolint + + }, + ) + + if err != nil { + return 0, err + } + exitCode := ctr.State().ExitCode + c.ContainerStateToDisk(ctr) + return exitCode, nil +} diff --git a/test/kpod_wait.bats b/test/kpod_wait.bats new file mode 100644 index 00000000..5bc396b8 --- /dev/null +++ b/test/kpod_wait.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE="redis:alpine" +ROOT="$TESTDIR/crio" +RUNROOT="$TESTDIR/crio-run" +KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT ${STORAGE_OPTS}" + + +# Returns the POD ID +function pod_run_from_template(){ + #1=name, 2=uid, 3=namespace) { + NAME=$1 CUID=$2 NAMESPACE=$3 envsubst < ${TESTDATA}/template_sandbox_config.json > ${TESTDIR}/pod-${1}.json + crioctl pod run --config ${TESTDIR}/pod-${1}.json +} + +# Returns the container ID +function container_create_from_template() { + #1=name, 2=image, 3=command, 4=id) { + NAME=$1 IMAGE=$2 COMMAND=$3 envsubst < ${TESTDATA}/template_container_config.json > ${TESTDIR}/ctr-${1}.json + crioctl ctr create --config ${TESTDIR}/ctr-${1}.json --pod "$4" +} + +function container_start() { + #1=id + crioctl ctr start --id "$1" + +} +@test "wait on a bogus container" { + start_crio + run ${KPOD_BINARY} ${KPOD_OPTIONS} wait 12343 + echo $output + [ "$status" -eq 1 ] + stop_crio +} + +@test "wait on a stopped container" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + echo $output + start_crio + pod_id=$( pod_run_from_template "test" "test" "test1-1" ) + echo $pod_id + ctr_id=$(container_create_from_template "test-CTR" "docker.io/library/busybox:latest" '["ls"]' "${pod_id}") + echo $ctr_id + container_start $ctr_id + run ${KPOD_BINARY} ${KPOD_OPTIONS} wait $ctr_id + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} + +@test "wait on a sleeping container" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + echo $output + start_crio + pod_id=$( pod_run_from_template "test" "test" "test1-1" ) + echo $pod_id + ctr_id=$(container_create_from_template "test-CTR" "docker.io/library/busybox:latest" '["sleep", "5"]' "${pod_id}") + echo $ctr_id + run container_start $ctr_id + echo $output + run ${KPOD_BINARY} ${KPOD_OPTIONS} wait $ctr_id + echo $output + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} diff --git a/test/testdata/template_container_config.json b/test/testdata/template_container_config.json new file mode 100644 index 00000000..a770a7c9 --- /dev/null +++ b/test/testdata/template_container_config.json @@ -0,0 +1,68 @@ +{ + "metadata": { + "name": "${NAME}", + "attempt": 1 + }, + "image": { + "image": "${IMAGE}" + }, + "command": ${COMMAND}, + "args": [], + "working_dir": "/", + "envs": [ + { + "key": "PATH", + "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + { + "key": "TERM", + "value": "xterm" + }, + { + "key": "TESTDIR", + "value": "test/dir1" + }, + { + "key": "TESTFILE", + "value": "test/file1" + } + ], + "labels": { + "type": "small", + "batch": "no" + }, + "annotations": { + "owner": "dragon", + "daemon": "crio" + }, + "privileged": true, + "log_path": "", + "stdin": false, + "stdin_once": false, + "tty": false, + "linux": { + "resources": { + "cpu_period": 10000, + "cpu_quota": 20000, + "cpu_shares": 512, + "oom_score_adj": 30 + }, + "security_context": { + "readonly_rootfs": false, + "selinux_options": { + "user": "system_u", + "role": "system_r", + "type": "svirt_lxc_net_t", + "level": "s0:c4,c5" + }, + "capabilities": { + "add_capabilities": [ + "setuid", + "setgid" + ], + "drop_capabilities": [ + ] + } + } + } +} diff --git a/test/testdata/template_sandbox_config.json b/test/testdata/template_sandbox_config.json new file mode 100644 index 00000000..f43ffb0d --- /dev/null +++ b/test/testdata/template_sandbox_config.json @@ -0,0 +1,51 @@ +{ + "metadata": { + "name": "${NAME}", + "uid": "${CUID}", + "namespace": "${NAMESPACE}", + "attempt": 1 + }, + "hostname": "crioctl_host", + "log_directory": "", + "dns_config": { + "searches": [ + "8.8.8.8" + ] + }, + "port_mappings": [], + "resources": { + "cpu": { + "limits": 3, + "requests": 2 + }, + "memory": { + "limits": 50000000, + "requests": 2000000 + } + }, + "labels": { + "group": "test" + }, + "annotations": { + "owner": "hmeng", + "security.alpha.kubernetes.io/sysctls": "kernel.shm_rmid_forced=1,net.ipv4.ip_local_port_range=1024 65000", + "security.alpha.kubernetes.io/unsafe-sysctls": "kernel.msgmax=8192" , + "security.alpha.kubernetes.io/seccomp/pod": "unconfined" + }, + "linux": { + "cgroup_parent": "/Burstable/pod_123-456", + "security_context": { + "namespace_options": { + "host_network": false, + "host_pid": false, + "host_ipc": false + }, + "selinux_options": { + "user": "system_u", + "role": "system_r", + "type": "svirt_lxc_net_t", + "level": "s0:c4,c5" + } + } + } +} diff --git a/transfer.md b/transfer.md index 674199df..077ecef4 100644 --- a/transfer.md +++ b/transfer.md @@ -58,5 +58,6 @@ There are other equivalents for these tools | `docker stop` | [`kpod stop`](./docs/kpod-stop.1.md) | | `docker unpause`| [`kpod unpause`](./docs/kpod-unpause.1.md)| | `docker version`| [`kpod version`](./docs/kpod-version.1.md)| +| `docker wait` | [`kpod wait`](./docs/kpod-wait.1.md)| *** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/kpod-cp.1.md) for more information.