summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/build.go10
-rw-r--r--cmd/podman/commit.go2
-rw-r--r--cmd/podman/common.go6
-rw-r--r--cmd/podman/create.go134
-rw-r--r--cmd/podman/create_cli.go90
-rw-r--r--cmd/podman/libpodruntime/runtime.go20
-rw-r--r--cmd/podman/main.go2
-rw-r--r--cmd/podman/run.go110
-rw-r--r--cmd/podman/wait.go15
9 files changed, 221 insertions, 168 deletions
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
index 0ca0b3d83..1b8a5faec 100644
--- a/cmd/podman/build.go
+++ b/cmd/podman/build.go
@@ -1,13 +1,13 @@
package main
import (
+ "github.com/containers/buildah"
+ "github.com/containers/buildah/imagebuildah"
+ buildahcli "github.com/containers/buildah/pkg/cli"
+ "github.com/containers/buildah/pkg/parse"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
- "github.com/projectatomic/buildah"
- "github.com/projectatomic/buildah/imagebuildah"
- buildahcli "github.com/projectatomic/buildah/pkg/cli"
- "github.com/projectatomic/buildah/pkg/parse"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"io/ioutil"
@@ -50,7 +50,7 @@ func getDockerfiles(files []string) []string {
}
func buildCmd(c *cli.Context) error {
- // The following was taken directly from projectatomic/buildah/cmd/bud.go
+ // The following was taken directly from containers/buildah/cmd/bud.go
// TODO Find a away to vendor more of this in rather than copy from bud
output := ""
diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go
index 517eefd07..234926de0 100644
--- a/cmd/podman/commit.go
+++ b/cmd/podman/commit.go
@@ -6,13 +6,13 @@ import (
"os"
"strings"
+ "github.com/containers/buildah"
"github.com/containers/image/manifest"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
- "github.com/projectatomic/buildah"
"github.com/urfave/cli"
)
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 578de7f38..9ab0e57e5 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -8,11 +8,11 @@ import (
"regexp"
"strings"
+ "github.com/containers/buildah"
"github.com/containers/libpod/libpod"
"github.com/containers/storage"
"github.com/fatih/camelcase"
"github.com/pkg/errors"
- "github.com/projectatomic/buildah"
"github.com/urfave/cli"
)
@@ -418,6 +418,10 @@ var createFlags = []cli.Flag{
Usage: "UTS namespace to use",
},
cli.StringSliceFlag{
+ Name: "mount",
+ Usage: "Attach a filesystem mount to the container (default [])",
+ },
+ cli.StringSliceFlag{
Name: "volume, v",
Usage: "Bind mount a volume into the container (default [])",
},
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index bc010d047..fc0c71536 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -24,6 +24,7 @@ import (
"github.com/docker/docker/pkg/signal"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -57,6 +58,30 @@ var createCommand = cli.Command{
}
func createCmd(c *cli.Context) error {
+ if err := createInit(c); err != nil {
+ return err
+ }
+
+ if os.Geteuid() != 0 {
+ rootless.SetSkipStorageSetup(true)
+ }
+
+ runtime, err := libpodruntime.GetContainerRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ ctr, _, err := createContainer(c, runtime)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("%s\n", ctr.ID())
+ return nil
+}
+
+func createInit(c *cli.Context) error {
// TODO should allow user to create based off a directory on the host not just image
// Need CLI support for this
@@ -83,63 +108,51 @@ func createCmd(c *cli.Context) error {
return errors.Errorf("image name or ID is required")
}
+ return nil
+}
+
+func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
+ rtc := runtime.GetConfig()
+ ctx := getContext()
rootfs := ""
if c.Bool("rootfs") {
rootfs = c.Args()[0]
}
- mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap"))
- if err != nil {
- return err
- }
- storageOpts, err := libpodruntime.GetDefaultStoreOptions()
- if err != nil {
- return err
- }
- storageOpts.UIDMap = mappings.UIDMap
- storageOpts.GIDMap = mappings.GIDMap
-
- if os.Geteuid() != 0 {
- rootless.SetSkipStorageSetup(true)
- }
-
- runtime, err := libpodruntime.GetRuntimeWithStorageOpts(c, &storageOpts)
- if err != nil {
- return errors.Wrapf(err, "error creating libpod runtime")
- }
- defer runtime.Shutdown(false)
-
- rtc := runtime.GetConfig()
- ctx := getContext()
-
imageName := ""
var data *inspect.ImageData = nil
+
if rootfs == "" && !rootless.SkipStorageSetup() {
newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false)
if err != nil {
- return err
+ return nil, nil, err
}
data, err = newImage.Inspect(ctx)
- imageName = newImage.Names()[0]
+ names := newImage.Names()
+ if len(names) > 0 {
+ imageName = names[0]
+ } else {
+ imageName = newImage.ID()
+ }
}
createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data)
if err != nil {
- return err
+ return nil, nil, err
}
runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig)
if err != nil {
- return err
+ return nil, nil, err
}
options, err := createConfig.GetContainerCreateOptions(runtime)
if err != nil {
- return err
+ return nil, nil, err
}
became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, runtime)
if err != nil {
- return err
+ return nil, nil, err
}
if became {
os.Exit(ret)
@@ -147,27 +160,25 @@ func createCmd(c *cli.Context) error {
ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...)
if err != nil {
- return err
+ return nil, nil, err
}
createConfigJSON, err := json.Marshal(createConfig)
if err != nil {
- return err
+ return nil, nil, err
}
if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil {
- return err
+ return nil, nil, err
}
- logrus.Debug("new container created ", ctr.ID())
-
if c.String("cidfile") != "" {
err := libpod.WriteFile(ctr.ID(), c.String("cidfile"))
if err != nil {
logrus.Error(err)
}
}
- fmt.Printf("%s\n", ctr.ID())
- return nil
+ logrus.Debugf("New container created %q", ctr.ID())
+ return ctr, createConfig, nil
}
// Checks if a user-specified AppArmor profile is loaded, or loads the default profile if
@@ -311,7 +322,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
}
}
}
- config.ProcessLabel, config.MountLabel, err = label.InitLabels(labelOpts)
+ config.LabelOpts = labelOpts
return err
}
@@ -449,6 +460,10 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
}
blkioWeight = uint16(u)
}
+ var mountList []spec.Mount
+ if mountList, err = parseMounts(c.StringSlice("mount")); err != nil {
+ return nil, err
+ }
if err = parseVolumes(c.StringSlice("volume")); err != nil {
return nil, err
@@ -518,17 +533,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
}
}
- shmDir := ""
- if ipcMode.IsHost() {
- shmDir = "/dev/shm"
- } else if ipcMode.IsContainer() {
- ctr, err := runtime.LookupContainer(ipcMode.Container())
- if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", ipcMode.Container())
- }
- shmDir = ctr.ShmDir()
- }
-
// USER
user := c.String("user")
if user == "" {
@@ -629,7 +633,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
command = append(command, data.ContainerConfig.Cmd...)
}
- if len(command) == 0 {
+ if data != nil && len(command) == 0 {
return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image")
}
@@ -681,7 +685,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
}
var systemd bool
- if c.BoolT("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) {
+ if command != nil && c.BoolT("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) {
systemd = true
if signalString == "" {
stopSignal, err = signal.ParseSignal("RTMIN+3")
@@ -765,7 +769,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
Ulimit: c.StringSlice("ulimit"),
},
Rm: c.Bool("rm"),
- ShmDir: shmDir,
StopSignal: stopSignal,
StopTimeout: c.Uint("stop-timeout"),
Sysctl: sysctl,
@@ -774,6 +777,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
Tty: tty,
User: user,
UsernsMode: usernsMode,
+ Mounts: mountList,
Volumes: c.StringSlice("volume"),
WorkDir: workDir,
Rootfs: rootfs,
@@ -796,6 +800,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
return config, nil
}
+type namespace interface {
+ IsContainer() bool
+ Container() string
+}
+
func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *libpod.Runtime) (bool, int, error) {
if os.Geteuid() == 0 {
return false, 0, nil
@@ -827,5 +836,26 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l
}
}
+ namespacesStr := []string{string(createConfig.IpcMode), string(createConfig.NetMode), string(createConfig.UsernsMode), string(createConfig.PidMode), string(createConfig.UtsMode)}
+ for _, i := range namespacesStr {
+ if cc.IsNS(i) {
+ return rootless.JoinNSPath(cc.NS(i))
+ }
+ }
+
+ namespaces := []namespace{createConfig.IpcMode, createConfig.NetMode, createConfig.UsernsMode, createConfig.PidMode, createConfig.UtsMode}
+ for _, i := range namespaces {
+ if i.IsContainer() {
+ ctr, err := runtime.LookupContainer(i.Container())
+ if err != nil {
+ return false, -1, err
+ }
+ pid, err := ctr.PID()
+ if err != nil {
+ return false, -1, err
+ }
+ return rootless.JoinNS(uint(pid))
+ }
+ }
return rootless.BecomeRootInUserNS()
}
diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go
index 812b62058..218e9b806 100644
--- a/cmd/podman/create_cli.go
+++ b/cmd/podman/create_cli.go
@@ -8,6 +8,8 @@ import (
cc "github.com/containers/libpod/pkg/spec"
"github.com/docker/docker/pkg/sysinfo"
+ "github.com/docker/go-units"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -74,6 +76,94 @@ func addWarning(warnings []string, msg string) []string {
return append(warnings, msg)
}
+// Format supported.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ..
+func parseMounts(mounts []string) ([]spec.Mount, error) {
+ var mountList []spec.Mount
+ errInvalidSyntax := errors.Errorf("incorrect mount format : should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>,[options]")
+ for _, mount := range mounts {
+ var tokenCount int
+ var mountInfo spec.Mount
+
+ arr := strings.SplitN(mount, ",", 2)
+ if len(arr) < 2 {
+ return nil, errInvalidSyntax
+ }
+ kv := strings.Split(arr[0], "=")
+ if kv[0] != "type" {
+ return nil, errInvalidSyntax
+ }
+ switch kv[1] {
+ case "bind":
+ mountInfo.Type = string(cc.TypeBind)
+ case "tmpfs":
+ mountInfo.Type = string(cc.TypeTmpfs)
+ mountInfo.Source = string(cc.TypeTmpfs)
+ mountInfo.Options = append(mountInfo.Options, []string{"rprivate", "noexec", "nosuid", "nodev", "size=65536k"}...)
+
+ default:
+ return nil, errors.Errorf("invalid filesystem type %q", kv[1])
+ }
+
+ tokens := strings.Split(arr[1], ",")
+ for i, val := range tokens {
+ if i == (tokenCount - 1) {
+ //Parse tokens before options.
+ break
+ }
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ mountInfo.Options = append(mountInfo.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ if mountInfo.Type != "bind" {
+ return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
+ }
+ mountInfo.Options = append(mountInfo.Options, kv[0])
+ case "tmpfs-mode":
+ if mountInfo.Type != "tmpfs" {
+ return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
+ }
+ mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if mountInfo.Type != "tmpfs" {
+ return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
+ }
+ shmSize, err := units.FromHumanSize(kv[1])
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to translate tmpfs-size")
+ }
+
+ mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("size=%d", shmSize))
+
+ case "bind-propagation":
+ if mountInfo.Type != "bind" {
+ return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
+ }
+ mountInfo.Options = append(mountInfo.Options, kv[1])
+ case "src", "source":
+ if mountInfo.Type == "tmpfs" {
+ return nil, errors.Errorf("can not use src= on a tmpfs file system")
+ }
+ if err := validateVolumeHostDir(kv[1]); err != nil {
+ return nil, err
+ }
+ mountInfo.Source = kv[1]
+ case "target", "dst", "destination":
+ if err := validateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ mountInfo.Destination = kv[1]
+ default:
+ return nil, errors.Errorf("incorrect mount option : %s", kv[0])
+ }
+ }
+ mountList = append(mountList, mountInfo)
+ }
+ return mountList, nil
+}
+
func parseVolumes(volumes []string) error {
for _, volume := range volumes {
arr := strings.SplitN(volume, ":", 3)
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go
index b7281ed8c..a0d497e8e 100644
--- a/cmd/podman/libpodruntime/runtime.go
+++ b/cmd/podman/libpodruntime/runtime.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/urfave/cli"
@@ -21,6 +22,21 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) {
return GetRuntimeWithStorageOpts(c, &storageOpts)
}
+// GetContainerRuntime generates a new libpod runtime configured by command line options for containers
+func GetContainerRuntime(c *cli.Context) (*libpod.Runtime, error) {
+ mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap"))
+ if err != nil {
+ return nil, err
+ }
+ storageOpts, err := GetDefaultStoreOptions()
+ if err != nil {
+ return nil, err
+ }
+ storageOpts.UIDMap = mappings.UIDMap
+ storageOpts.GIDMap = mappings.GIDMap
+ return GetRuntimeWithStorageOpts(c, &storageOpts)
+}
+
func GetRootlessStorageOpts() (storage.StoreOptions, error) {
var opts storage.StoreOptions
@@ -120,7 +136,9 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions
if c.GlobalIsSet("default-mounts-file") {
options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file")))
}
- options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path"), c.GlobalIsSet("hooks-dir-path")))
+ if c.GlobalIsSet("hooks-dir-path") {
+ options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path")))
+ }
// TODO flag to set CNI plugins dir?
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index a532bc26e..840650a3f 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -147,6 +147,8 @@ func main() {
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil {
return errors.Wrapf(err, "error setting new rlimits")
}
+ } else {
+ logrus.Info("running as rootless")
}
if logLevel == "debug" {
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index 3445daef5..f9a96e4a6 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -1,7 +1,6 @@
package main
import (
- "encoding/json"
"fmt"
"io/ioutil"
"os"
@@ -11,11 +10,7 @@ import (
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/libpod/image"
- "github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/rootless"
- cc "github.com/containers/libpod/pkg/spec"
- "github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
@@ -42,108 +37,24 @@ var runCommand = cli.Command{
}
func runCmd(c *cli.Context) error {
- var imageName string
-
- // Docker-compatibility: the "-h" flag for run/create is reserved for
- // the hostname (see https://github.com/containers/libpod/issues/1367).
- if c.Bool("help") {
- cli.ShowCommandHelpAndExit(c, "run", 0)
- }
-
- if err := validateFlags(c, createFlags); err != nil {
- return err
- }
-
- if c.String("cidfile") != "" {
- if _, err := os.Stat(c.String("cidfile")); err == nil {
- return errors.Errorf("container id file exists. ensure another container is not using it or delete %s", c.String("cidfile"))
- }
- if err := libpod.WriteFile("", c.String("cidfile")); err != nil {
- return errors.Wrapf(err, "unable to write cidfile %s", c.String("cidfile"))
- }
- }
-
- storageOpts, err := libpodruntime.GetDefaultStoreOptions()
- if err != nil {
- return err
- }
- mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap"))
- if err != nil {
+ if err := createInit(c); err != nil {
return err
}
- storageOpts.UIDMap = mappings.UIDMap
- storageOpts.GIDMap = mappings.GIDMap
-
if os.Geteuid() != 0 {
rootless.SetSkipStorageSetup(true)
}
- runtime, err := libpodruntime.GetRuntimeWithStorageOpts(c, &storageOpts)
+ runtime, err := libpodruntime.GetContainerRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
- if len(c.Args()) < 1 {
- return errors.Errorf("image name or ID is required")
- }
-
- rootfs := ""
- if c.Bool("rootfs") {
- rootfs = c.Args()[0]
- }
-
- ctx := getContext()
- rtc := runtime.GetConfig()
-
- var newImage *image.Image = nil
- var data *inspect.ImageData = nil
- if rootfs == "" && !rootless.SkipStorageSetup() {
- newImage, err = runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false)
- if err != nil {
- return errors.Wrapf(err, "unable to find image")
- }
-
- data, err = newImage.Inspect(ctx)
- if err != nil {
- return err
- }
- if len(newImage.Names()) < 1 {
- imageName = newImage.ID()
- } else {
- imageName = newImage.Names()[0]
- }
- }
- createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data)
+ ctr, createConfig, err := createContainer(c, runtime)
if err != nil {
return err
}
- runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig)
- if err != nil {
- return err
- }
-
- options, err := createConfig.GetContainerCreateOptions(runtime)
- if err != nil {
- return err
- }
-
- became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, runtime)
- if err != nil {
- return err
- }
- if became {
- os.Exit(ret)
- }
-
- ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...)
- if err != nil {
- return err
- }
-
- logrus.Debugf("New container created %q", ctr.ID())
-
if logrus.GetLevel() == logrus.DebugLevel {
cgroupPath, err := ctr.CGroupPath()
if err == nil {
@@ -151,20 +62,7 @@ func runCmd(c *cli.Context) error {
}
}
- createConfigJSON, err := json.Marshal(createConfig)
- if err != nil {
- return err
- }
- if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil {
- return err
- }
-
- if c.String("cidfile") != "" {
- if err := libpod.WriteFile(ctr.ID(), c.String("cidfile")); err != nil {
- logrus.Error(err)
- }
- }
-
+ ctx := getContext()
// Handle detached start
if createConfig.Detach {
if err := ctr.Start(ctx); err != nil {
diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go
index e919ab3ca..07db20eee 100644
--- a/cmd/podman/wait.go
+++ b/cmd/podman/wait.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
+ "time"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/pkg/errors"
@@ -15,7 +16,14 @@ var (
Block until one or more containers stop and then print their exit codes
`
- waitFlags = []cli.Flag{LatestFlag}
+ waitFlags = []cli.Flag{
+ cli.UintFlag{
+ Name: "interval, i",
+ Usage: "Milliseconds to wait before polling for completion",
+ Value: 250,
+ },
+ LatestFlag,
+ }
waitCommand = cli.Command{
Name: "wait",
Usage: "Block on one or more containers",
@@ -57,7 +65,10 @@ func waitCmd(c *cli.Context) error {
if err != nil {
return errors.Wrapf(err, "unable to find container %s", container)
}
- returnCode, err := ctr.Wait()
+ if c.Uint("interval") == 0 {
+ return errors.Errorf("interval must be greater then 0")
+ }
+ returnCode, err := ctr.WaitWithInterval(time.Duration(c.Uint("interval")) * time.Millisecond)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)