From c65b3599cc3ab6972f1b5d96f3e712cd86d74833 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 26 Oct 2018 16:42:28 +0200 Subject: runtime: do not allow runroot longer than 50 characters Signed-off-by: Giuseppe Scrivano --- cmd/podman/libpodruntime/runtime.go | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'cmd') diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index df422eb81..a4b3581be 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" + "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -42,6 +43,9 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions if c.GlobalIsSet("runroot") { storageOpts.RunRoot = c.GlobalString("runroot") } + if len(storageOpts.RunRoot) > 50 { + return nil, errors.New("the specified runroot is longer than 50 characters") + } if c.GlobalIsSet("storage-driver") { storageOpts.GraphDriverName = c.GlobalString("storage-driver") } -- cgit v1.2.3-54-g00ecf From b2fef1a8badb875339d6329c095842baee4b3a17 Mon Sep 17 00:00:00 2001 From: Anders F Björklund Date: Tue, 30 Oct 2018 23:55:48 +0100 Subject: Fix setting of version information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was setting the wrong variable (CamelCase) in the wrong module ("main", not "libpod")... Signed-off-by: Anders F Björklund --- Makefile | 3 ++- cmd/podman/info.go | 3 ++- cmd/podman/version.go | 2 +- libpod/version.go | 10 +++++----- pkg/varlinkapi/system.go | 13 ++++++++----- 5 files changed, 18 insertions(+), 13 deletions(-) (limited to 'cmd') diff --git a/Makefile b/Makefile index b3e2fcc26..d8a870fa2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,8 @@ PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/c COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}") BUILD_INFO ?= $(shell date +%s) -LDFLAGS_PODMAN ?= $(LDFLAGS) -X main.gitCommit=$(GIT_COMMIT) -X main.buildInfo=$(BUILD_INFO) +LIBPOD := ${PROJECT}/libpod +LDFLAGS_PODMAN ?= $(LDFLAGS) -X $(LIBPOD).gitCommit=$(GIT_COMMIT) -X $(LIBPOD).buildInfo=$(BUILD_INFO) ISODATE ?= $(shell date --iso-8601) LIBSECCOMP_COMMIT := release-2.3 diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 563e63ba3..c0639725e 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -81,6 +81,7 @@ func debugInfo(c *cli.Context) map[string]interface{} { info["compiler"] = runtime.Compiler info["go version"] = runtime.Version() info["podman version"] = c.App.Version - info["git commit"] = libpod.GitCommit + version, _ := libpod.GetVersion() + info["git commit"] = version.GitCommit return info } diff --git a/cmd/podman/version.go b/cmd/podman/version.go index f896229c4..d80f24a14 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -21,7 +21,7 @@ func versionCmd(c *cli.Context) error { fmt.Println("Git Commit: ", output.GitCommit) } // Prints out the build time in readable format - if libpod.BuildInfo != "" { + if output.Built != 0 { fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) } diff --git a/libpod/version.go b/libpod/version.go index 5e7cd83c9..966588ae9 100644 --- a/libpod/version.go +++ b/libpod/version.go @@ -11,10 +11,10 @@ import ( var ( // GitCommit is the commit that the binary is being built from. // It will be populated by the Makefile. - GitCommit string + gitCommit string // BuildInfo is the time at which the binary was built // It will be populated by the Makefile. - BuildInfo string + buildInfo string ) //Version is an output struct for varlink @@ -30,9 +30,9 @@ type Version struct { func GetVersion() (Version, error) { var err error var buildTime int64 - if BuildInfo != "" { + if buildInfo != "" { // Converts unix time from string to int64 - buildTime, err = strconv.ParseInt(BuildInfo, 10, 64) + buildTime, err = strconv.ParseInt(buildInfo, 10, 64) if err != nil { return Version{}, err @@ -41,7 +41,7 @@ func GetVersion() (Version, error) { return Version{ Version: podmanVersion.Version, GoVersion: runtime.Version(), - GitCommit: GitCommit, + GitCommit: gitCommit, Built: buildTime, OsArch: runtime.GOOS + "/" + runtime.GOARCH, }, nil diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 287f42209..a29d22e7d 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -34,6 +34,10 @@ func (i *LibpodAPI) Ping(call iopodman.VarlinkCall) error { // GetInfo returns details about the podman host and its stores func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { + versionInfo, err := libpod.GetVersion() + if err != nil { + return err + } var ( registries, insecureRegistries []string ) @@ -64,11 +68,10 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Host = infoHost store := info[1].Data pmaninfo := iopodman.InfoPodmanBinary{ - Compiler: goruntime.Compiler, - Go_version: goruntime.Version(), - // TODO : How are we going to get this here? - //Podman_version: - Git_commit: libpod.GitCommit, + Compiler: goruntime.Compiler, + Go_version: goruntime.Version(), + Podman_version: versionInfo.Version, + Git_commit: versionInfo.GitCommit, } graphStatus := iopodman.InfoGraphStatus{ -- cgit v1.2.3-54-g00ecf From 2011782d9d52958546e481f84892d00a548b9e12 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 29 Oct 2018 12:06:48 -0500 Subject: Make restart parallel and add --all When attempting to restart many containers, we can benefit from making the restarts parallel. For convenience, two new options are added: --all attempts to restart all containers --run-only when used with --all will attempt to restart only running containers Signed-off-by: baude --- cmd/podman/restart.go | 95 +++++++++++++++++++++++++++++++------------ cmd/podman/shared/parallel.go | 18 ++++---- completions/bash/podman | 7 +++- docs/podman-restart.1.md | 26 ++++++++++-- test/e2e/restart_test.go | 40 ++++++++++++++++++ 5 files changed, 146 insertions(+), 40 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 7b48ef24e..2e264db79 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,18 +1,26 @@ package main import ( - "context" "fmt" - "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( restartFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "restart all non-running containers", + }, + cli.BoolFlag{ + Name: "running", + Usage: "restart only running containers when --all is used", + }, cli.UintFlag{ Name: "timeout, time, t", Usage: "Seconds to wait for stop before killing the container", @@ -35,11 +43,19 @@ var ( ) func restartCmd(c *cli.Context) error { + var ( + restartFuncs []shared.ParallelWorkerInput + containers []*libpod.Container + lastError error + restartContainers []*libpod.Container + ) + args := c.Args() - if len(args) < 1 && !c.Bool("latest") { + runOnly := c.Bool("running") + all := c.Bool("all") + if len(args) < 1 && !c.Bool("latest") && !all { return errors.Wrapf(libpod.ErrInvalidArg, "you must provide at least one container name or ID") } - if err := validateFlags(c, restartFlags); err != nil { return err } @@ -50,8 +66,6 @@ func restartCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var lastError error - timeout := c.Uint("timeout") useTimeout := c.IsSet("timeout") @@ -59,39 +73,66 @@ func restartCmd(c *cli.Context) error { if c.Bool("latest") { lastCtr, err := runtime.GetLatestContainer() if err != nil { - lastError = errors.Wrapf(err, "unable to get latest container") - } else { - ctrTimeout := lastCtr.StopTimeout() - if useTimeout { - ctrTimeout = timeout - } - - lastError = lastCtr.RestartWithTimeout(context.TODO(), ctrTimeout) + return errors.Wrapf(err, "unable to get latest container") } - } - - for _, id := range args { - ctr, err := runtime.LookupContainer(id) + restartContainers = append(restartContainers, lastCtr) + } else if runOnly { + containers, err = getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + restartContainers = append(restartContainers, containers...) + } else if all { + containers, err = runtime.GetAllContainers() + if err != nil { + return err + } + restartContainers = append(restartContainers, containers...) + } else { + for _, id := range args { + ctr, err := runtime.LookupContainer(id) + if err != nil { + return err } - lastError = errors.Wrapf(err, "unable to find container %s", id) - continue + restartContainers = append(restartContainers, ctr) } + } + // We now have a slice of all the containers to be restarted. Iterate them to + // create restart Funcs with a timeout as needed + for _, ctr := range restartContainers { + con := ctr ctrTimeout := ctr.StopTimeout() if useTimeout { ctrTimeout = timeout } - if err := ctr.RestartWithTimeout(context.TODO(), ctrTimeout); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error restarting container %s", ctr.ID()) + f := func() error { + return con.RestartWithTimeout(getContext(), ctrTimeout) } + + restartFuncs = append(restartFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("restart") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + restartErrors := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) + + for cid, result := range restartErrors { + if result != nil { + fmt.Println(result.Error()) + lastError = result + continue + } + fmt.Println(cid) + } return lastError } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 03eba2f0b..dbf43a982 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -72,20 +72,22 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { - case "stop": - if numCpus <= 2 { - return 4 - } else { - return numCpus * 3 - } + case "ps": + return 8 + case "restart": + return numCpus * 2 case "rm": if numCpus <= 3 { return numCpus * 3 } else { return numCpus * 4 } - case "ps": - return 8 + case "stop": + if numCpus <= 2 { + return 4 + } else { + return numCpus * 3 + } } return 3 } diff --git a/completions/bash/podman b/completions/bash/podman index 5cfed348f..ed4e080c9 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1770,8 +1770,13 @@ _podman_restart() { --timeout -t " local boolean_options=" + --all + -a --latest - -l" + -l + --running + --timeout + -t" case "$cur" in -*) COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) diff --git a/docs/podman-restart.1.md b/docs/podman-restart.1.md index caacaf31d..875afa385 100644 --- a/docs/podman-restart.1.md +++ b/docs/podman-restart.1.md @@ -12,33 +12,51 @@ Containers will be stopped if they are running and then restarted. Stopped containers will not be stopped and will only be started. ## OPTIONS -**--timeout** - -Timeout to wait before forcibly stopping the container +**--all, -a** +Restart all containers regardless of their current state. **--latest, -l** - Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. +**--running** +Restart all containers that are already in the *running* state. + +**--timeout** +Timeout to wait before forcibly stopping the container. + + ## EXAMPLES ## +Restart the latest container ``` $ podman restart -l ec588fc80b05e19d3006bf2e8aa325f0a2e2ff1f609b7afb39176ca8e3e13467 ``` +Restart a specific container by partial container ID ``` $ podman restart ff6cf1 ff6cf1e5e77e6dba1efc7f3fcdb20e8b89ad8947bc0518be1fcb2c78681f226f ``` +Restart two containers by name with a timeout of 4 seconds ``` $ podman restart --timeout 4 test1 test2 c3bb026838c30e5097f079fa365c9a4769d52e1017588278fa00d5c68ebc1502 17e13a63081a995136f907024bcfe50ff532917988a152da229db9d894c5a9ec ``` +Restart all running containers +``` +$ podman restart --running +``` + +Restart all containers +``` +$ podman restart --all +``` + ## SEE ALSO podman(1), podman-run(1), podman-start(1), podman-create(1) diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index d2fc35485..eca2bbcda 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -136,4 +136,44 @@ var _ = Describe("Podman restart", func() { Expect(timeSince < 10*time.Second).To(BeTrue()) Expect(timeSince > 2*time.Second).To(BeTrue()) }) + + It("Podman restart --all", func() { + _, exitCode, _ := podmanTest.RunLsContainer("test1") + Expect(exitCode).To(Equal(0)) + + test2 := podmanTest.RunTopContainer("test2") + test2.WaitWithDefaultTimeout() + Expect(test2.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"restart", "-all"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Not(Equal(startTime.OutputToStringArray()[0]))) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) + + It("Podman restart --all --running", func() { + _, exitCode, _ := podmanTest.RunLsContainer("test1") + Expect(exitCode).To(Equal(0)) + + test2 := podmanTest.RunTopContainer("test2") + test2.WaitWithDefaultTimeout() + Expect(test2.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"restart", "-a", "--running"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Equal(startTime.OutputToStringArray()[0])) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) }) -- cgit v1.2.3-54-g00ecf From b559c19c2fa739cf1c8ede50eab8f5acf74f6bf3 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 29 Oct 2018 13:53:39 -0500 Subject: Make kill, pause, and unpause parallel. Operations like kill, pause, and unpause -- which can operation on one or more containers -- can greatly benefit from parallizing its main job (eq kill). In the case of pauseand unpause, an --all option as was added. pause --all will pause all **running** containers. And unpause --all will unpause all **paused** containers. Signed-off-by: baude --- cmd/podman/kill.go | 51 +++++++++++++++++++++++------- cmd/podman/pause.go | 72 +++++++++++++++++++++++++++++++++--------- cmd/podman/shared/parallel.go | 15 +++++++++ cmd/podman/stop.go | 4 ++- cmd/podman/unpause.go | 73 ++++++++++++++++++++++++++++++++++--------- completions/bash/podman | 9 ++++++ docs/podman-kill.1.md | 2 +- docs/podman-pause.1.md | 19 ++++++++++- docs/podman-unpause.1.md | 20 +++++++++++- test/e2e/pause_test.go | 62 ++++++++++++++++++++++++++++++++++++ 10 files changed, 282 insertions(+), 45 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 7ca5bd7c5..27882aeee 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -1,15 +1,16 @@ package main import ( - "os" + "fmt" "syscall" - "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -41,6 +42,12 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { + var ( + lastError error + killFuncs []shared.ParallelWorkerInput + killSignal uint = uint(syscall.SIGTERM) + ) + if err := checkAllAndLatest(c); err != nil { return err } @@ -56,7 +63,6 @@ func killCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - var killSignal uint = uint(syscall.SIGTERM) if c.String("signal") != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err @@ -67,17 +73,40 @@ func killCmd(c *cli.Context) error { killSignal = uint(sysSignal) } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") - + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + return err + } for _, ctr := range containers { - if err := ctr.Kill(killSignal); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + con := ctr + f := func() error { + return con.Kill(killSignal) + } + + killFuncs = append(killFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("kill") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + killErrors := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + + for cid, result := range killErrors { + if result != nil { + if len(killErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "unable to find container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } + return lastError } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 203fa6070..1e1585216 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -5,11 +5,20 @@ import ( "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + pauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "pause all running containers", + }, + } pauseDescription = ` podman pause @@ -19,6 +28,7 @@ var ( Name: "pause", Usage: "Pauses all the processes in one or more containers", Description: pauseDescription, + Flags: pauseFlags, Action: pauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +36,11 @@ var ( ) func pauseCmd(c *cli.Context) error { + var ( + lastError error + pauseContainers []*libpod.Container + pauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("pause is not supported for rootless containers") } @@ -37,28 +52,55 @@ func pauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + pauseContainers = append(pauseContainers, containers...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + pauseContainers = append(pauseContainers, ctr) + } + } + + // Now assemble the slice of pauseFuncs + for _, ctr := range pauseContainers { + con := ctr + + f := func() error { + return con.Pause() } - if err = ctr.Pause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + pauseFuncs = append(pauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("pause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + pauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + + for cid, result := range pauseErrors { + if result != nil { + if len(pauseErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "failed to pause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } return lastError } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index dbf43a982..633781a45 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -72,6 +72,16 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map func Parallelize(job string) int { numCpus := runtime.NumCPU() switch job { + case "kill": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "pause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 case "ps": return 8 case "restart": @@ -88,6 +98,11 @@ func Parallelize(job string) int { } else { return numCpus * 3 } + case "unpause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 } return 3 } diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index afeb49f76..cb36fd5cd 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -89,7 +89,9 @@ func stopCmd(c *cli.Context) error { for cid, result := range stopErrors { if result != nil && result != libpod.ErrCtrStopped { - fmt.Println(result.Error()) + if len(stopErrors) > 1 { + fmt.Println(result.Error()) + } lastError = result continue } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index a792aaf6d..648fc9d3d 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -5,11 +5,20 @@ import ( "os" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var ( + unpauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "unpause all paused containers", + }, + } unpauseDescription = ` podman unpause @@ -19,6 +28,7 @@ var ( Name: "unpause", Usage: "Unpause the processes in one or more containers", Description: unpauseDescription, + Flags: unpauseFlags, Action: unpauseCmd, ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", OnUsageError: usageErrorHandler, @@ -26,6 +36,11 @@ var ( ) func unpauseCmd(c *cli.Context) error { + var ( + lastError error + unpauseContainers []*libpod.Container + unpauseFuncs []shared.ParallelWorkerInput + ) if os.Geteuid() != 0 { return errors.New("unpause is not supported for rootless containers") } @@ -37,28 +52,56 @@ func unpauseCmd(c *cli.Context) error { defer runtime.Shutdown(false) args := c.Args() - if len(args) < 1 { + if len(args) < 1 && !c.Bool("all") { return errors.Errorf("you must provide at least one container name or id") } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) + if c.Bool("all") { + cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + return err + } + unpauseContainers = append(unpauseContainers, cs...) + } else { + for _, arg := range args { + ctr, err := runtime.LookupContainer(arg) + if err != nil { + return err } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue + unpauseContainers = append(unpauseContainers, ctr) } - if err = ctr.Unpause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) + } + + // Assemble the unpause funcs + for _, ctr := range unpauseContainers { + con := ctr + f := func() error { + return con.Unpause() + } + + unpauseFuncs = append(unpauseFuncs, shared.ParallelWorkerInput{ + ContainerID: con.ID(), + ParallelFunc: f, + }) + } + + maxWorkers := shared.Parallelize("unpause") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalInt("max-workers") + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + unpauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + + for cid, result := range unpauseErrors { + if result != nil && result != libpod.ErrCtrStopped { + if len(unpauseErrors) > 1 { + fmt.Println(result.Error()) } - lastError = errors.Wrapf(err, "failed to unpause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) + lastError = result + continue } + fmt.Println(cid) } + return lastError } diff --git a/completions/bash/podman b/completions/bash/podman index ed4e080c9..c029f893a 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1,5 +1,6 @@ : ${PROG:=$(basename ${BASH_SOURCE})} + __podman_previous_extglob_setting=$(shopt -p extglob) shopt -s extglob @@ -1934,6 +1935,10 @@ _podman_save() { } _podman_pause() { + local boolean_options=" + -a + --all + " local options_with_args=" --help -h " @@ -2035,6 +2040,10 @@ _podman_stop() { } _podman_unpause() { + local boolean_options=" + -a + --all + " local options_with_args=" --help -h " diff --git a/docs/podman-kill.1.md b/docs/podman-kill.1.md index 14066d151..85f68a73d 100644 --- a/docs/podman-kill.1.md +++ b/docs/podman-kill.1.md @@ -4,7 +4,7 @@ podman\-kill - Kills one or more containers with a signal ## SYNOPSIS -**podman kill** [*options*] *container* ... +**podman kill** [*options*] [*container* ...] ## DESCRIPTION The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal. diff --git a/docs/podman-pause.1.md b/docs/podman-pause.1.md index b4930de8d..f19fa5d6a 100644 --- a/docs/podman-pause.1.md +++ b/docs/podman-pause.1.md @@ -4,16 +4,33 @@ podman\-pause - Pause one or more containers ## SYNOPSIS -**podman pause** [*options*] *container* ... +**podman pause** [*options*] [*container*...] ## DESCRIPTION Pauses all the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Pause all running containers. + ## EXAMPLE +Pause a container named 'mywebserver' +``` podman pause mywebserver +``` +Pause a container by partial container ID. +``` podman pause 860a4b23 +``` + +Pause all **running** containers. +``` +podman stop -a +``` ## SEE ALSO podman(1), podman-unpause(1) diff --git a/docs/podman-unpause.1.md b/docs/podman-unpause.1.md index 9404e7648..acfab0930 100644 --- a/docs/podman-unpause.1.md +++ b/docs/podman-unpause.1.md @@ -4,16 +4,34 @@ podman\-unpause - Unpause one or more containers ## SYNOPSIS -**podman unpause** [*options*] *container* ... +**podman unpause** [*options*] [*container*...] ## DESCRIPTION Unpauses the processes in one or more containers. You may use container IDs or names as input. +## OPTIONS + +**--all, -a** + +Unpause all paused containers. + ## EXAMPLE +Unpause a container called 'mywebserver' +``` podman unpause mywebserver +``` +Unpause a container by a partial container ID. + +``` podman unpause 860a4b23 +``` + +Unpause all **paused** containers. +``` +podman unpause -a +``` ## SEE ALSO podman(1), podman-pause(1) diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index c34964f59..24876b6d6 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -213,4 +213,66 @@ var _ = Describe("Podman pause", func() { result.WaitWithDefaultTimeout() }) + It("Pause all containers (no containers exist)", func() { + result := podmanTest.Podman([]string{"pause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + + }) + + It("Unpause all containers (no paused containers exist)", func() { + result := podmanTest.Podman([]string{"unpause", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("Pause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + running = podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(0)) + }) + + It("Unpause a bunch of running containers", func() { + podmanTest.RestoreArtifact(nginx) + for i := 0; i < 3; i++ { + name := fmt.Sprintf("test%d", i) + run := podmanTest.Podman([]string{"run", "-dt", "--name", name, nginx}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + } + pause := podmanTest.Podman([]string{"pause", "--all"}) + pause.WaitWithDefaultTimeout() + Expect(pause.ExitCode()).To(Equal(0)) + + unpause := podmanTest.Podman([]string{"unpause", "--all"}) + unpause.WaitWithDefaultTimeout() + Expect(unpause.ExitCode()).To(Equal(0)) + + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(3)) + }) + }) -- cgit v1.2.3-54-g00ecf From 59f93edacc483039c6a3ecd8b35817dcd9515d17 Mon Sep 17 00:00:00 2001 From: Šimon Lukašík Date: Sat, 3 Nov 2018 17:20:54 +0100 Subject: --interactive shall keep STDIN attached even when not explicitly called out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addressing: podman run -it -a STDERR --rm alpine /bin/ash hanging. As we droped stdin as soon as -a was used. Notice this is contrary to what D-tool does and contrary to what podman help implies: podman run --help | grep interact --interactive, -i Keep STDIN open even if not attached Signed-off-by: Šimon Lukašík --- cmd/podman/run.go | 2 -- 1 file changed, 2 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/run.go b/cmd/podman/run.go index e4b25eaf4..af6ced45d 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -96,8 +96,6 @@ func runCmd(c *cli.Context) error { inputStream = nil } - inputStream = nil - attachTo := c.StringSlice("attach") for _, stream := range attachTo { switch strings.ToLower(stream) { -- cgit v1.2.3-54-g00ecf From 35438b6c86edf19916daa0ca031af57d3f1fcf2b Mon Sep 17 00:00:00 2001 From: Urvashi Mohnani Date: Wed, 7 Nov 2018 08:53:10 +0000 Subject: Set --force-rm for podman build to true by default Since we use buildah containers for the build process, the user will not know if we have any buildah containers lingering due to a failed build. Setting this to true by default till we figure out a better way to solve this. Signed-off-by: Urvashi Mohnani --- cmd/podman/build.go | 15 +- vendor.conf | 2 +- vendor/github.com/containers/buildah/README.md | 1 + vendor/github.com/containers/buildah/buildah.go | 1 + vendor/github.com/containers/buildah/chroot/run.go | 2 +- vendor/github.com/containers/buildah/common.go | 28 +++- vendor/github.com/containers/buildah/delete.go | 3 +- vendor/github.com/containers/buildah/image.go | 1 - .../containers/buildah/imagebuildah/build.go | 98 +++++------ .../buildah/imagebuildah/chroot_symlink.go | 133 +++++++++++++-- vendor/github.com/containers/buildah/new.go | 81 ++++++---- .../containers/buildah/pkg/cli/common.go | 8 +- vendor/github.com/containers/buildah/pull.go | 5 +- vendor/github.com/containers/buildah/run.go | 180 +++++++++++++++++---- .../containers/buildah/unshare/unshare.go | 4 + vendor/github.com/containers/buildah/util/util.go | 61 +------ vendor/github.com/containers/buildah/vendor.conf | 6 +- 17 files changed, 428 insertions(+), 201 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 14bf226f9..880cb892f 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,6 +1,11 @@ package main import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" @@ -10,14 +15,14 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" - "io/ioutil" - "os" - "path/filepath" - "strings" ) var ( layerFlags = []cli.Flag{ + cli.BoolTFlag{ + Name: "force-rm", + Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful. (default true)", + }, cli.BoolTFlag{ Name: "layers", Usage: "cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. ", @@ -230,7 +235,7 @@ func buildCmd(c *cli.Context) error { Layers: layers, NoCache: c.Bool("no-cache"), RemoveIntermediateCtrs: c.BoolT("rm"), - ForceRmIntermediateCtrs: c.Bool("force-rm"), + ForceRmIntermediateCtrs: c.BoolT("force-rm"), } if c.Bool("quiet") { diff --git a/vendor.conf b/vendor.conf index 85b784d9b..c3998b5f2 100644 --- a/vendor.conf +++ b/vendor.conf @@ -92,7 +92,7 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/ k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils github.com/mrunalp/fileutils master github.com/varlink/go master -github.com/containers/buildah 46c577c87d5a7ab30ef40cfa695cd2b96b32b117 +github.com/containers/buildah 795d43e60e5a1ab283981b79eeda1dd14a65a0bd github.com/Nvveen/Gotty master github.com/fsouza/go-dockerclient master github.com/openshift/imagebuilder master diff --git a/vendor/github.com/containers/buildah/README.md b/vendor/github.com/containers/buildah/README.md index 6a79e524b..2b539bba8 100644 --- a/vendor/github.com/containers/buildah/README.md +++ b/vendor/github.com/containers/buildah/README.md @@ -107,6 +107,7 @@ $ sudo ./lighttpd.sh | [buildah-images(1)](/docs/buildah-images.md) | List images in local storage. | | [buildah-inspect(1)](/docs/buildah-inspect.md) | Inspects the configuration of a container or image. | | [buildah-mount(1)](/docs/buildah-mount.md) | Mount the working container's root filesystem. | +| [buildah-pull(1)](/docs/buildah-pull.md) | Pull an image from the specified location. | | [buildah-push(1)](/docs/buildah-push.md) | Push an image from local storage to elsewhere. | | [buildah-rename(1)](/docs/buildah-rename.md) | Rename a local container. | | [buildah-rm(1)](/docs/buildah-rm.md) | Removes one or more working containers. | diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 9994d6cd0..1a642ed3d 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -224,6 +224,7 @@ func GetBuildInfo(b *Builder) BuilderInfo { ContainerID: b.ContainerID, MountPoint: b.MountPoint, ProcessLabel: b.ProcessLabel, + MountLabel: b.MountLabel, ImageAnnotations: b.ImageAnnotations, ImageCreatedBy: b.ImageCreatedBy, OCIv1: b.OCIv1, diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index 51e2d2bd4..8cfefb3de 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -1147,7 +1147,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( } if uintptr(fs.Flags)&expectedFlags != expectedFlags { if err := unix.Mount(target, target, "bind", requestFlags|unix.MS_REMOUNT, ""); err != nil { - return undoBinds, errors.Wrapf(err, "error remounting %q in mount namespace with expected flags") + return undoBinds, errors.Wrapf(err, "error remounting %q in mount namespace with expected flags", target) } } } diff --git a/vendor/github.com/containers/buildah/common.go b/vendor/github.com/containers/buildah/common.go index 56a901925..be59215df 100644 --- a/vendor/github.com/containers/buildah/common.go +++ b/vendor/github.com/containers/buildah/common.go @@ -2,12 +2,14 @@ package buildah import ( "io" - - "github.com/sirupsen/logrus" + "os" + "path/filepath" cp "github.com/containers/image/copy" "github.com/containers/image/transports" "github.com/containers/image/types" + "github.com/containers/libpod/pkg/rootless" + "github.com/sirupsen/logrus" ) const ( @@ -17,10 +19,20 @@ const ( DOCKER = "docker" ) +// userRegistriesFile is the path to the per user registry configuration file. +var userRegistriesFile = filepath.Join(os.Getenv("HOME"), ".config/containers/registries.conf") + func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference, sourceSystemContext *types.SystemContext, destinationReference types.ImageReference, destinationSystemContext *types.SystemContext, manifestType string) *cp.Options { sourceCtx := &types.SystemContext{} if sourceSystemContext != nil { *sourceCtx = *sourceSystemContext + } else { + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + sourceCtx.SystemRegistriesConfPath = userRegistriesFile + } + + } } sourceInsecure, err := isReferenceInsecure(sourceReference, sourceCtx) if err != nil { @@ -33,6 +45,12 @@ func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference destinationCtx := &types.SystemContext{} if destinationSystemContext != nil { *destinationCtx = *destinationSystemContext + } else { + if rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + destinationCtx.SystemRegistriesConfPath = userRegistriesFile + } + } } destinationInsecure, err := isReferenceInsecure(destinationReference, destinationCtx) if err != nil { @@ -58,5 +76,11 @@ func getSystemContext(defaults *types.SystemContext, signaturePolicyPath string) if signaturePolicyPath != "" { sc.SignaturePolicyPath = signaturePolicyPath } + if sc.SystemRegistriesConfPath == "" && rootless.IsRootless() { + if _, err := os.Stat(userRegistriesFile); err == nil { + sc.SystemRegistriesConfPath = userRegistriesFile + } + + } return sc } diff --git a/vendor/github.com/containers/buildah/delete.go b/vendor/github.com/containers/buildah/delete.go index 25f76cf74..e3bddba20 100644 --- a/vendor/github.com/containers/buildah/delete.go +++ b/vendor/github.com/containers/buildah/delete.go @@ -1,7 +1,6 @@ package buildah import ( - "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" ) @@ -14,5 +13,5 @@ func (b *Builder) Delete() error { b.MountPoint = "" b.Container = "" b.ContainerID = "" - return label.ReleaseLabel(b.ProcessLabel) + return nil } diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 31aff9eea..c0bf90ddd 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -107,7 +107,6 @@ func expectedDockerDiffIDs(image docker.V2Image) int { // compression that we'll be applying. func (i *containerImageRef) computeLayerMIMEType(what string) (omediaType, dmediaType string, err error) { omediaType = v1.MediaTypeImageLayer - //TODO: Convert to manifest.DockerV2Schema2LayerUncompressedMediaType once available dmediaType = docker.V2S2MediaTypeUncompressedLayer if i.compression != archive.Uncompressed { switch i.compression { diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 41d85cbc6..292ff9541 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -563,39 +563,39 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { registry: options.Registry, transport: options.Transport, ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions, - quiet: options.Quiet, - runtime: options.Runtime, - runtimeArgs: options.RuntimeArgs, - transientMounts: options.TransientMounts, - compression: options.Compression, - output: options.Output, - outputFormat: options.OutputFormat, - additionalTags: options.AdditionalTags, - signaturePolicyPath: options.SignaturePolicyPath, - systemContext: options.SystemContext, - volumeCache: make(map[string]string), - volumeCacheInfo: make(map[string]os.FileInfo), - log: options.Log, - in: options.In, - out: options.Out, - err: options.Err, - reportWriter: options.ReportWriter, - isolation: options.Isolation, - namespaceOptions: options.NamespaceOptions, - configureNetwork: options.ConfigureNetwork, - cniPluginPath: options.CNIPluginPath, - cniConfigDir: options.CNIConfigDir, - idmappingOptions: options.IDMappingOptions, - commonBuildOptions: options.CommonBuildOpts, - defaultMountsFilePath: options.DefaultMountsFilePath, - iidfile: options.IIDFile, - squash: options.Squash, - labels: append([]string{}, options.Labels...), - annotations: append([]string{}, options.Annotations...), - layers: options.Layers, - noCache: options.NoCache, - removeIntermediateCtrs: options.RemoveIntermediateCtrs, - forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, + quiet: options.Quiet, + runtime: options.Runtime, + runtimeArgs: options.RuntimeArgs, + transientMounts: options.TransientMounts, + compression: options.Compression, + output: options.Output, + outputFormat: options.OutputFormat, + additionalTags: options.AdditionalTags, + signaturePolicyPath: options.SignaturePolicyPath, + systemContext: options.SystemContext, + volumeCache: make(map[string]string), + volumeCacheInfo: make(map[string]os.FileInfo), + log: options.Log, + in: options.In, + out: options.Out, + err: options.Err, + reportWriter: options.ReportWriter, + isolation: options.Isolation, + namespaceOptions: options.NamespaceOptions, + configureNetwork: options.ConfigureNetwork, + cniPluginPath: options.CNIPluginPath, + cniConfigDir: options.CNIConfigDir, + idmappingOptions: options.IDMappingOptions, + commonBuildOptions: options.CommonBuildOpts, + defaultMountsFilePath: options.DefaultMountsFilePath, + iidfile: options.IIDFile, + squash: options.Squash, + labels: append([]string{}, options.Labels...), + annotations: append([]string{}, options.Annotations...), + layers: options.Layers, + noCache: options.NoCache, + removeIntermediateCtrs: options.RemoveIntermediateCtrs, + forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, } if exec.err == nil { exec.err = os.Stderr @@ -764,7 +764,7 @@ func (b *Executor) resolveNameToImageRef() (types.ImageReference, error) { if err != nil { candidates, _, err := util.ResolveName(b.output, "", b.systemContext, b.store) if err != nil { - return nil, errors.Wrapf(err, "error parsing target image name %q: %v", b.output) + return nil, errors.Wrapf(err, "error parsing target image name %q", b.output) } if len(candidates) == 0 { return nil, errors.Errorf("error parsing target image name %q", b.output) @@ -1044,17 +1044,14 @@ func (b *Executor) copiedFilesMatch(node *parser.Node, historyTime *time.Time) ( } continue } - // For local files, walk the file tree and check the time stamps. - timeIsGreater := false - err := filepath.Walk(item, func(path string, info os.FileInfo, err error) error { - if info.ModTime().After(*historyTime) { - timeIsGreater = true - return nil - } - return nil - }) + // Walks the file tree for local files and uses chroot to ensure we don't escape out of the allowed path + // when resolving any symlinks. + // Change the time format to ensure we don't run into a parsing error when converting again from string + // to time.Time. It is a known Go issue that the conversions cause errors sometimes, so specifying a particular + // time format here when converting to a string. + timeIsGreater, err := resolveModifiedTime(b.contextDir, item, historyTime.Format(time.RFC3339Nano)) if err != nil { - return false, errors.Wrapf(err, "error walking file tree %q", item) + return false, errors.Wrapf(err, "error resolving symlinks and comparing modified times: %q", item) } if timeIsGreater { return false, nil @@ -1289,15 +1286,24 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt } else { // If the Dockerfile isn't found try prepending the // context directory to it. - if _, err := os.Stat(dfile); os.IsNotExist(err) { + dinfo, err := os.Stat(dfile) + if os.IsNotExist(err) { dfile = filepath.Join(options.ContextDirectory, dfile) } + dinfo, err = os.Stat(dfile) + if err != nil { + return "", nil, errors.Wrapf(err, "error reading info about %q", dfile) + } + // If given a directory, add '/Dockerfile' to it. + if dinfo.Mode().IsDir() { + dfile = filepath.Join(dfile, "Dockerfile") + } logrus.Debugf("reading local Dockerfile %q", dfile) contents, err := os.Open(dfile) if err != nil { return "", nil, errors.Wrapf(err, "error reading %q", dfile) } - dinfo, err := contents.Stat() + dinfo, err = contents.Stat() if err != nil { contents.Close() return "", nil, errors.Wrapf(err, "error reading info about %q", dfile) diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go index 20e396f1f..2269b8dcc 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go +++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/containers/storage/pkg/reexec" "github.com/pkg/errors" @@ -14,13 +15,18 @@ import ( const ( symlinkChrootedCommand = "chrootsymlinks-resolve" + symlinkModifiedTime = "modtimesymlinks-resolve" maxSymlinksResolved = 40 ) func init() { reexec.Register(symlinkChrootedCommand, resolveChrootedSymlinks) + reexec.Register(symlinkModifiedTime, resolveSymlinkTimeModified) } +// main() for grandparent subprocess. Its main job is to shuttle stdio back +// and forth, managing a pseudo-terminal if we want one, for our child, the +// parent subprocess. func resolveChrootedSymlinks() { status := 0 flag.Parse() @@ -39,7 +45,7 @@ func resolveChrootedSymlinks() { } // Our second parameter is the path name to evaluate for symbolic links - symLink, err := getSymbolicLink(flag.Arg(0), flag.Arg(1)) + symLink, err := getSymbolicLink(flag.Arg(1)) if err != nil { fmt.Fprintf(os.Stderr, "error getting symbolic links: %v\n", err) os.Exit(1) @@ -51,7 +57,8 @@ func resolveChrootedSymlinks() { os.Exit(status) } -// ResolveSymlink resolves any symlink in filename in the context of rootdir. +// ResolveSymLink (in the grandparent process) resolves any symlink in filename +// in the context of rootdir. func ResolveSymLink(rootdir, filename string) (string, error) { // The child process expects a chroot and one path that // will be consulted relative to the chroot directory and evaluated @@ -62,32 +69,121 @@ func ResolveSymLink(rootdir, filename string) (string, error) { return "", errors.Wrapf(err, string(output)) } - // Hand back the resolved symlink, will be "" if a symlink is not found + // Hand back the resolved symlink, will be filename if a symlink is not found return string(output), nil } +// main() for grandparent subprocess. Its main job is to shuttle stdio back +// and forth, managing a pseudo-terminal if we want one, for our child, the +// parent subprocess. +func resolveSymlinkTimeModified() { + status := 0 + flag.Parse() + if len(flag.Args()) < 1 { + os.Exit(1) + } + // Our first parameter is the directory to chroot into. + if err := unix.Chdir(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chdir(): %v\n", err) + os.Exit(1) + } + if err := unix.Chroot(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chroot(): %v\n", err) + os.Exit(1) + } + + // Our second parameter is the path name to evaluate for symbolic links. + // Our third parameter is the time the cached intermediate image was created. + // We check whether the modified time of the filepath we provide is after the time the cached image was created. + timeIsGreater, err := modTimeIsGreater(flag.Arg(0), flag.Arg(1), flag.Arg(2)) + if err != nil { + fmt.Fprintf(os.Stderr, "error checking if modified time of resolved symbolic link is greater: %v\n", err) + os.Exit(1) + } + if _, err := os.Stdout.WriteString(fmt.Sprintf("%v", timeIsGreater)); err != nil { + fmt.Fprintf(os.Stderr, "error writing string to stdout: %v\n", err) + os.Exit(1) + } + os.Exit(status) +} + +// resolveModifiedTime (in the grandparent process) checks filename for any symlinks, +// resolves it and compares the modified time of the file with historyTime, which is +// the creation time of the cached image. It returns true if filename was modified after +// historyTime, otherwise returns false. +func resolveModifiedTime(rootdir, filename, historyTime string) (bool, error) { + // The child process expects a chroot and one path that + // will be consulted relative to the chroot directory and evaluated + // for any symbolic links present. + cmd := reexec.Command(symlinkModifiedTime, rootdir, filename, historyTime) + output, err := cmd.CombinedOutput() + if err != nil { + return false, errors.Wrapf(err, string(output)) + } + // Hand back true/false depending on in the file was modified after the caches image was created. + return string(output) == "true", nil +} + +// modTimeIsGreater goes through the files added/copied in using the Dockerfile and +// checks the time stamp (follows symlinks) with the time stamp of when the cached +// image was created. IT compares the two and returns true if the file was modified +// after the cached image was created, otherwise it returns false. +func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) { + var timeIsGreater bool + + // Convert historyTime from string to time.Time for comparison + histTime, err := time.Parse(time.RFC3339Nano, historyTime) + if err != nil { + return false, errors.Wrapf(err, "error converting string to time.Time %q", historyTime) + } + // Walk the file tree and check the time stamps. + // Since we are chroot in rootdir, only want the path of the actual filename, i.e path - rootdir. + // +1 to account for the extra "/" (e.g rootdir=/home/user/mydir, path=/home/user/mydir/myfile.json) + err = filepath.Walk(path[len(rootdir)+1:], func(path string, info os.FileInfo, err error) error { + modTime := info.ModTime() + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + // Evaluate any symlink that occurs to get updated modified information + resolvedPath, err := filepath.EvalSymlinks(path) + if err != nil { + return errors.Wrapf(err, "error evaluating symlink %q", path) + } + fileInfo, err := os.Stat(resolvedPath) + if err != nil { + return errors.Wrapf(err, "error getting file info %q", resolvedPath) + } + modTime = fileInfo.ModTime() + } + if modTime.After(histTime) { + timeIsGreater = true + return nil + } + return nil + }) + if err != nil { + return false, errors.Wrapf(err, "error walking file tree %q", path) + } + return timeIsGreater, err +} + // getSymbolic link goes through each part of the path and continues resolving symlinks as they appear. // Returns what the whole target path for what "path" resolves to. -func getSymbolicLink(rootdir, path string) (string, error) { +func getSymbolicLink(path string) (string, error) { var ( symPath string symLinksResolved int ) - - // Splitting path as we need to resolve each parth of the path at a time + // Splitting path as we need to resolve each part of the path at a time splitPath := strings.Split(path, "/") if splitPath[0] == "" { splitPath = splitPath[1:] symPath = "/" } - for _, p := range splitPath { // If we have resolved 40 symlinks, that means something is terribly wrong // will return an error and exit if symLinksResolved >= maxSymlinksResolved { return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved) } - symPath = filepath.Join(symPath, p) isSymlink, resolvedPath, err := hasSymlink(symPath) if err != nil { @@ -119,16 +215,21 @@ func getSymbolicLink(rootdir, path string) (string, error) { // otherwise it returns false and path func hasSymlink(path string) (bool, string, error) { info, err := os.Lstat(path) - if os.IsNotExist(err) { - if err = os.MkdirAll(path, 0755); err != nil { - return false, "", errors.Wrapf(err, "error ensuring volume path %q exists", path) - } - info, err = os.Lstat(path) - if err != nil { - return false, "", errors.Wrapf(err, "error running lstat on %q", path) + if err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(path, 0755); err != nil { + return false, "", errors.Wrapf(err, "error ensuring volume path %q exists", path) + } + info, err = os.Lstat(path) + if err != nil { + return false, "", errors.Wrapf(err, "error running lstat on %q", path) + } + } else { + return false, path, errors.Wrapf(err, "error get stat of path %q", path) } } - // Return false and path as path is not a symlink + + // Return false and path as path if not a symlink if info.Mode()&os.ModeSymlink != os.ModeSymlink { return false, path, nil } diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 8b0e774ba..13bebf420 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -3,6 +3,7 @@ package buildah import ( "context" "fmt" + "math/rand" "strings" "github.com/containers/buildah/util" @@ -12,7 +13,6 @@ import ( "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage" - "github.com/opencontainers/selinux/go-selinux/label" "github.com/openshift/imagebuilder" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -23,11 +23,6 @@ const ( // as "no image". BaseImageFakeName = imagebuilder.NoBaseImageSpecifier - // DefaultTransport is a prefix that we apply to an image name if we - // can't find one in the local Store, in order to generate a source - // reference for the image that we can then copy to the local Store. - DefaultTransport = "docker://" - // minimumTruncatedIDLength is the minimum length of an identifier that // we'll accept as possibly being a truncated image ID. minimumTruncatedIDLength = 3 @@ -150,7 +145,7 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store } logrus.Debugf("error parsing image name %q as given, trying with transport %q: %v", image, options.Transport, err) transport := options.Transport - if transport != DefaultTransport { + if transport != util.DefaultTransport { transport = transport + ":" } srcRef2, err := alltransports.ParseImageName(transport + image) @@ -232,6 +227,27 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store } } +func containerNameExist(name string, containers []storage.Container) bool { + for _, container := range containers { + for _, cname := range container.Names { + if cname == name { + return true + } + } + } + return false +} + +func findUnusedContainer(name string, containers []storage.Container) string { + suffix := 1 + tmpName := name + for containerNameExist(tmpName, containers) { + tmpName = fmt.Sprintf("%s-%d", name, suffix) + suffix++ + } + return tmpName +} + func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { var ref types.ImageReference var img *storage.Image @@ -241,7 +257,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions options.FromImage = "" } if options.Transport == "" { - options.Transport = DefaultTransport + options.Transport = util.DefaultTransport } systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath) @@ -277,23 +293,33 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions name = imageNamePrefix(image) + "-" + name } } + var container *storage.Container + tmpName := name + if options.Container == "" { + containers, err := store.Containers() + if err != nil { + return nil, errors.Wrapf(err, "unable to check for container names") + } + tmpName = findUnusedContainer(tmpName, containers) + } - coptions := storage.ContainerOptions{} - coptions.IDMappingOptions = newContainerIDMappingOptions(options.IDMappingOptions) - - container, err := store.CreateContainer("", []string{name}, imageID, "", "", &coptions) - suffix := 1 - for err != nil && errors.Cause(err) == storage.ErrDuplicateName && options.Container == "" { - suffix++ - tmpName := fmt.Sprintf("%s-%d", name, suffix) - if container, err = store.CreateContainer("", []string{tmpName}, imageID, "", "", &coptions); err == nil { + conflict := 100 + for true { + coptions := storage.ContainerOptions{ + LabelOpts: options.CommonBuildOpts.LabelOpts, + IDMappingOptions: newContainerIDMappingOptions(options.IDMappingOptions), + } + container, err = store.CreateContainer("", []string{tmpName}, imageID, "", "", &coptions) + if err == nil { name = tmpName + break } + if errors.Cause(err) != storage.ErrDuplicateName || options.Container != "" { + return nil, errors.Wrapf(err, "error creating container") + } + tmpName = fmt.Sprintf("%s-%d", name, rand.Int()%conflict) + conflict = conflict * 10 } - if err != nil { - return nil, errors.Wrapf(err, "error creating container") - } - defer func() { if err != nil { if err2 := store.DeleteContainer(container.ID); err2 != nil { @@ -302,13 +328,6 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } }() - if err = ReserveSELinuxLabels(store, container.ID); err != nil { - return nil, err - } - processLabel, mountLabel, err := label.InitLabels(options.CommonBuildOpts.LabelOpts) - if err != nil { - return nil, err - } uidmap, gidmap := convertStorageIDMaps(container.UIDMap, container.GIDMap) defaultNamespaceOptions, err := DefaultNamespaceOptions() @@ -328,8 +347,8 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions ContainerID: container.ID, ImageAnnotations: map[string]string{}, ImageCreatedBy: "", - ProcessLabel: processLabel, - MountLabel: mountLabel, + ProcessLabel: container.ProcessLabel(), + MountLabel: container.MountLabel(), DefaultMountsFilePath: options.DefaultMountsFilePath, Isolation: options.Isolation, NamespaceOptions: namespaceOptions, @@ -351,7 +370,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } if options.Mount { - _, err = builder.Mount(mountLabel) + _, err = builder.Mount(container.MountLabel()) if err != nil { return nil, errors.Wrapf(err, "error mounting build container %q", builder.ContainerID) } diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index b54663f5d..03b340294 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -70,6 +70,10 @@ var ( } LayerFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "force-rm", + Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful.", + }, cli.BoolFlag{ Name: "layers", Usage: fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers()), @@ -115,10 +119,6 @@ var ( Name: "file, f", Usage: "`pathname or URL` of a Dockerfile", }, - cli.BoolFlag{ - Name: "force-rm", - Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful.", - }, cli.StringFlag{ Name: "format", Usage: "`format` of the built image's manifest and metadata. Use BUILDAH_FORMAT environment variable to override.", diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index 52269541a..1a51edb0e 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -146,11 +146,11 @@ func pullImage(ctx context.Context, store storage.Store, imageName string, optio srcRef, err := alltransports.ParseImageName(spec) if err != nil { if options.Transport == "" { - options.Transport = DefaultTransport + options.Transport = util.DefaultTransport } logrus.Debugf("error parsing image name %q, trying with transport %q: %v", spec, options.Transport, err) transport := options.Transport - if transport != DefaultTransport { + if transport != util.DefaultTransport { transport = transport + ":" } spec = transport + spec @@ -201,6 +201,7 @@ func pullImage(ctx context.Context, store storage.Store, imageName string, optio logrus.Debugf("copying %q to %q", spec, destName) if _, err := cp.Image(ctx, policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, srcRef, sc, destRef, nil, "")); err != nil { + logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", spec, destName, err) return nil, err } return destRef, nil diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 718ef4e36..636a204b3 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -451,7 +451,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. copyWithTar := b.copyWithTar(nil, nil) - builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes) + builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, copyWithTar, builtinVolumes, int(rootUID), int(rootGID)) if err != nil { return err } @@ -493,15 +493,21 @@ func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts return mounts, nil } -func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string) ([]specs.Mount, error) { +func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWithTar func(srcPath, dstPath string) error, builtinVolumes []string, rootUID, rootGID int) ([]specs.Mount, error) { var mounts []specs.Mount + hostOwner := idtools.IDPair{UID: rootUID, GID: rootGID} // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. for _, volume := range builtinVolumes { subdir := digest.Canonical.FromString(volume).Hex() volumePath := filepath.Join(containerDir, "buildah-volumes", subdir) + srcPath := filepath.Join(mountPoint, volume) + initializeVolume := false // If we need to, initialize the volume path's initial contents. - if _, err := os.Stat(volumePath); err != nil && os.IsNotExist(err) { + if _, err := os.Stat(volumePath); err != nil { + if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "failed to stat %q for volume %q", volumePath, volume) + } logrus.Debugf("setting up built-in volume at %q", volumePath) if err = os.MkdirAll(volumePath, 0755); err != nil { return nil, errors.Wrapf(err, "error creating directory %q for volume %q", volumePath, volume) @@ -509,11 +515,21 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, copyWit if err = label.Relabel(volumePath, mountLabel, false); err != nil { return nil, errors.Wrapf(err, "error relabeling directory %q for volume %q", volumePath, volume) } - srcPath := filepath.Join(mountPoint, volume) - stat, err := os.Stat(srcPath) - if err != nil { + initializeVolume = true + } + stat, err := os.Stat(srcPath) + if err != nil { + if !os.IsNotExist(err) { return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume) } + if err = idtools.MkdirAllAndChownNew(srcPath, 0755, hostOwner); err != nil { + return nil, errors.Wrapf(err, "error creating directory %q for volume %q", srcPath, volume) + } + if stat, err = os.Stat(srcPath); err != nil { + return nil, errors.Wrapf(err, "failed to stat %q for volume %q", srcPath, volume) + } + } + if initializeVolume { if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil { return nil, errors.Wrapf(err, "failed to chmod %q for volume %q", volumePath, volume) } @@ -1044,24 +1060,31 @@ func (b *Builder) Run(command []string, options RunOptions) error { } rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} - hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair) - if err != nil { - return err - } - resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair) - if err != nil { - return err - } + bindFiles := make(map[string]string) + namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...) + volumes := b.Volumes() - if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil { - return err + if !contains(volumes, "/etc/hosts") { + hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair) + if err != nil { + return err + } + bindFiles["/etc/hosts"] = hostFile + + if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil { + return err + } } - bindFiles := map[string]string{ - "/etc/hosts": hostFile, - "/etc/resolv.conf": resolvFile, + if !contains(volumes, "/etc/resolv.conf") { + resolvFile, err := b.addNetworkConfig(path, "/etc/resolv.conf", rootIDPair) + if err != nil { + return err + } + bindFiles["/etc/resolv.conf"] = resolvFile } - err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, append(b.NamespaceOptions, options.NamespaceOptions...)) + + err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize, namespaceOptions) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID) } @@ -1095,7 +1118,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { } else { moreCreateArgs = nil } - err = b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path)) case IsolationChroot: err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr) case IsolationOCIRootless: @@ -1109,13 +1132,22 @@ func (b *Builder) Run(command []string, options RunOptions) error { } } options.Args = append(options.Args, rootlessFlag...) - err = b.runUsingRuntimeSubproc(options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path)) default: err = errors.Errorf("don't know how to run this command") } return err } +func contains(volumes []string, v string) bool { + for _, i := range volumes { + if i == v { + return true + } + } + return false +} + func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) error { switch isolation { case IsolationOCIRootless: @@ -1123,10 +1155,22 @@ func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) logrus.Debugf("Forcing use of an IPC namespace.") } options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.IPCNamespace)}) - if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil && !ns.Host { - logrus.Debugf("Disabling network namespace.") + _, err := exec.LookPath("slirp4netns") + hostNetworking := err != nil + networkNamespacePath := "" + if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil { + hostNetworking = ns.Host + networkNamespacePath = ns.Path + if !hostNetworking && networkNamespacePath != "" && !filepath.IsAbs(networkNamespacePath) { + logrus.Debugf("Disabling network namespace configuration.") + networkNamespacePath = "" + } } - options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.NetworkNamespace), Host: true}) + options.NamespaceOptions.AddOrReplace(NamespaceOption{ + Name: string(specs.NetworkNamespace), + Host: hostNetworking, + Path: networkNamespacePath, + }) if ns := options.NamespaceOptions.Find(string(specs.PIDNamespace)); ns == nil || ns.Host { logrus.Debugf("Forcing use of a PID namespace.") } @@ -1227,9 +1271,10 @@ type runUsingRuntimeSubprocOptions struct { ConfigureNetworks []string MoreCreateArgs []string ContainerName string + Isolation Isolation } -func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { +func (b *Builder) runUsingRuntimeSubproc(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) { var confwg sync.WaitGroup config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{ Options: options, @@ -1240,6 +1285,7 @@ func (b *Builder) runUsingRuntimeSubproc(options RunOptions, configureNetwork bo ConfigureNetworks: configureNetworks, MoreCreateArgs: moreCreateArgs, ContainerName: containerName, + Isolation: isolation, }) if conferr != nil { return errors.Wrapf(conferr, "error encoding configuration for %q", runUsingRuntimeCommand) @@ -1318,7 +1364,7 @@ func runUsingRuntimeMain() { os.Exit(1) } // Run the container, start to finish. - status, err := runUsingRuntime(options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) + status, err := runUsingRuntime(options.Isolation, options.Options, options.ConfigureNetwork, options.ConfigureNetworks, options.MoreCreateArgs, options.Spec, options.RootPath, options.BundlePath, options.ContainerName) if err != nil { fmt.Fprintf(os.Stderr, "error running container: %v\n", err) os.Exit(1) @@ -1333,7 +1379,7 @@ func runUsingRuntimeMain() { os.Exit(1) } -func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { +func runUsingRuntime(isolation Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (wstatus unix.WaitStatus, err error) { // Lock the caller to a single OS-level thread. runtime.LockOSThread() @@ -1490,7 +1536,7 @@ func runUsingRuntime(options RunOptions, configureNetwork bool, configureNetwork }() if configureNetwork { - teardown, err := runConfigureNetwork(options, configureNetworks, pid, containerName, spec.Process.Args) + teardown, err := runConfigureNetwork(isolation, options, configureNetworks, pid, containerName, spec.Process.Args) if teardown != nil { defer teardown() } @@ -1623,9 +1669,81 @@ func runCollectOutput(fds, closeBeforeReadingFds []int) string { } return b.String() } +func setupRootlessNetwork(pid int) (teardown func(), err error) { + slirp4netns, err := exec.LookPath("slirp4netns") + if err != nil { + return nil, errors.Wrapf(err, "cannot find slirp4netns") + } + + rootlessSlirpSyncR, rootlessSlirpSyncW, err := os.Pipe() + if err != nil { + return nil, errors.Wrapf(err, "cannot create slirp4netns sync pipe") + } + defer rootlessSlirpSyncR.Close() + + // Be sure there are no fds inherited to slirp4netns except the sync pipe + files, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return nil, errors.Wrapf(err, "cannot list open fds") + } + for _, f := range files { + fd, err := strconv.Atoi(f.Name()) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse fd") + } + if fd == int(rootlessSlirpSyncW.Fd()) { + continue + } + unix.CloseOnExec(fd) + } + + cmd := exec.Command(slirp4netns, "-r", "3", "-c", fmt.Sprintf("%d", pid), "tap0") + cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil + cmd.ExtraFiles = []*os.File{rootlessSlirpSyncW} + + err = cmd.Start() + rootlessSlirpSyncW.Close() + if err != nil { + return nil, errors.Wrapf(err, "cannot start slirp4netns") + } -func runConfigureNetwork(options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) { + b := make([]byte, 1) + for { + if err := rootlessSlirpSyncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil { + return nil, errors.Wrapf(err, "error setting slirp4netns pipe timeout") + } + if _, err := rootlessSlirpSyncR.Read(b); err == nil { + break + } else { + if os.IsTimeout(err) { + // Check if the process is still running. + var status syscall.WaitStatus + _, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to read slirp4netns process status") + } + if status.Exited() || status.Signaled() { + return nil, errors.New("slirp4netns failed") + } + + continue + } + return nil, errors.Wrapf(err, "failed to read from slirp4netns sync pipe") + } + } + + return func() { + cmd.Process.Kill() + cmd.Wait() + }, nil +} + +func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetworks []string, pid int, containerName string, command []string) (teardown func(), err error) { var netconf, undo []*libcni.NetworkConfigList + + if isolation == IsolationOCIRootless { + return setupRootlessNetwork(pid) + } // Scan for CNI configuration files. confdir := options.CNIConfigDir files, err := libcni.ConfFiles(confdir, []string{".conf"}) @@ -1956,7 +2074,7 @@ func runAcceptTerminal(consoleListener *net.UnixListener, terminalSize *specs.Bo for i := range scm { fds, err := unix.ParseUnixRights(&scm[i]) if err != nil { - return -1, errors.Wrapf(err, "error parsing unix rights control message: %v") + return -1, errors.Wrapf(err, "error parsing unix rights control message: %v", &scm[i]) } logrus.Debugf("fds: %v", fds) if len(fds) == 0 { diff --git a/vendor/github.com/containers/buildah/unshare/unshare.go b/vendor/github.com/containers/buildah/unshare/unshare.go index fbe623660..2a970b8d6 100644 --- a/vendor/github.com/containers/buildah/unshare/unshare.go +++ b/vendor/github.com/containers/buildah/unshare/unshare.go @@ -55,6 +55,10 @@ func (c *Cmd) Start() error { } c.Env = append(c.Env, fmt.Sprintf("_Buildah-unshare=%d", c.UnshareFlags)) + // Please the libpod "rootless" package to find the expected env variables. + c.Env = append(c.Env, "_LIBPOD_USERNS_CONFIGURED=done") + c.Env = append(c.Env, fmt.Sprintf("_LIBPOD_ROOTLESS_UID=%d", os.Geteuid())) + // Create the pipe for reading the child's PID. pidRead, pidWrite, err := os.Pipe() if err != nil { diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 3a415a7f3..b2451b78b 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -7,10 +7,8 @@ import ( "net/url" "os" "path" - "path/filepath" "strconv" "strings" - "syscall" "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" @@ -31,6 +29,10 @@ import ( const ( minimumTruncatedIDLength = 3 + // DefaultTransport is a prefix that we apply to an image name if we + // can't find one in the local Store, in order to generate a source + // reference for the image that we can then copy to the local Store. + DefaultTransport = "docker://" ) var ( @@ -89,6 +91,7 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } } + name = strings.TrimPrefix(name, DefaultTransport) // If the image name already included a domain component, we're done. named, err := reference.ParseNormalizedNamed(name) if err != nil { @@ -450,60 +453,6 @@ func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, return uid, gid, nil } -// UnsharedRootPath returns a location under ($XDG_DATA_HOME/containers/storage, -// or $HOME/.local/share/containers/storage, or -// (the user's home directory)/.local/share/containers/storage, or an error. -func UnsharedRootPath(homedir string) (string, error) { - // If $XDG_DATA_HOME is defined... - if envDataHome, haveDataHome := os.LookupEnv("XDG_DATA_HOME"); haveDataHome { - return filepath.Join(envDataHome, "containers", "storage"), nil - } - // If $XDG_DATA_HOME is not defined, but $HOME is defined... - if envHomedir, haveHomedir := os.LookupEnv("HOME"); haveHomedir { - // Default to the user's $HOME/.local/share/containers/storage subdirectory. - return filepath.Join(envHomedir, ".local", "share", "containers", "storage"), nil - } - // If we know where our home directory is... - if homedir != "" { - // Default to the user's homedir/.local/share/containers/storage subdirectory. - return filepath.Join(homedir, ".local", "share", "containers", "storage"), nil - } - return "", errors.New("unable to determine a --root location: neither $XDG_DATA_HOME nor $HOME is set") -} - -// UnsharedRunrootPath returns $XDG_RUNTIME_DIR/run, /var/run/user/(the user's UID)/run, or an error. -func UnsharedRunrootPath(uid string) (string, error) { - // If $XDG_RUNTIME_DIR is defined... - if envRuntimeDir, haveRuntimeDir := os.LookupEnv("XDG_RUNTIME_DIR"); haveRuntimeDir { - return filepath.Join(envRuntimeDir, "run"), nil - } - var runtimeDir string - // If $XDG_RUNTIME_DIR is not defined, but we know our UID... - if uid != "" { - tmpDir := filepath.Join("/var/run/user", uid) - os.MkdirAll(tmpDir, 0700) - st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { - runtimeDir = tmpDir - } - } - if runtimeDir == "" { - home := os.Getenv("HOME") - if home == "" { - return "", errors.New("neither XDG_RUNTIME_DIR nor HOME was set non-empty") - } - resolvedHome, err := filepath.EvalSymlinks(home) - if err != nil { - return "", errors.Wrapf(err, "cannot resolve %s", home) - } - runtimeDir = filepath.Join(resolvedHome, "rundir") - } - if err := os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { - return "", errors.New("could not set XDG_RUNTIME_DIR") - } - return runtimeDir, nil -} - // GetPolicyContext sets up, initializes and returns a new context for the specified policy func GetPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) { policy, err := signature.DefaultPolicy(ctx) diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index d79412afc..c4410d0af 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -4,8 +4,8 @@ github.com/BurntSushi/toml master github.com/containerd/continuity master github.com/containernetworking/cni v0.7.0-alpha1 github.com/containers/image 5e5b67d6b1cf43cc349128ec3ed7d5283a6cc0d1 -github.com/containers/libpod 2afadeec6696fefac468a49c8ba24b0bc275aa75 -github.com/containers/storage 41294c85d97bef688e18f710402895dbecde3308 +github.com/containers/libpod e75469ab99c48e9fbe2b36ade229d384bdea9144 +github.com/containers/storage 09abf3a26b8a3aa69e29fd7faeb260b98d675759 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1 @@ -36,7 +36,7 @@ github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc master github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-tools master -github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a +github.com/opencontainers/selinux master github.com/openshift/imagebuilder master github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460 github.com/pborman/uuid master -- cgit v1.2.3-54-g00ecf From 2dd9cae37cb076418393ba61c0fb7b8cf97148f3 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 7 Nov 2018 13:20:43 -0600 Subject: rm -f now removes a paused container We now can remove a paused container by sending it a kill signal while it is paused. We then unpause the container and it is immediately killed. Also, reworked how the parallelWorker results are handled to provide a more consistent approach to how each subcommand implements it. It also fixes a bug where if one container errors, the error message is duplicated when printed out. Signed-off-by: baude --- cmd/podman/kill.go | 23 +++++++---------------- cmd/podman/pause.go | 17 ++--------------- cmd/podman/restart.go | 16 ++-------------- cmd/podman/rm.go | 26 +++++++++++--------------- cmd/podman/shared/parallel.go | 10 +++++++--- cmd/podman/stop.go | 23 +++++++++-------------- cmd/podman/unpause.go | 18 ++---------------- cmd/podman/utils.go | 17 +++++++++++++++++ docs/podman-rm.1.md | 19 ++++++++++++++++--- libpod/runtime_ctr.go | 14 +++++++++++++- test/e2e/pause_test.go | 19 ++++++++++--------- 11 files changed, 96 insertions(+), 106 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 27882aeee..cfe4b4218 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -43,7 +43,6 @@ var ( // killCmd kills one or more containers with a signal func killCmd(c *cli.Context) error { var ( - lastError error killFuncs []shared.ParallelWorkerInput killSignal uint = uint(syscall.SIGTERM) ) @@ -75,8 +74,12 @@ func killCmd(c *cli.Context) error { containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") if err != nil { - return err + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) } + for _, ctr := range containers { con := ctr f := func() error { @@ -95,18 +98,6 @@ func killCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - killErrors := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) - - for cid, result := range killErrors { - if result != nil { - if len(killErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - - return lastError + killErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) + return printParallelOutput(killErrors, errCount) } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 1e1585216..fcb2f3cb8 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -37,7 +36,6 @@ var ( func pauseCmd(c *cli.Context) error { var ( - lastError error pauseContainers []*libpod.Container pauseFuncs []shared.ParallelWorkerInput ) @@ -90,17 +88,6 @@ func pauseCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - pauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) - - for cid, result := range pauseErrors { - if result != nil { - if len(pauseErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + pauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) + return printParallelOutput(pauseErrors, errCount) } diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 2e264db79..630493ef4 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -46,7 +44,6 @@ func restartCmd(c *cli.Context) error { var ( restartFuncs []shared.ParallelWorkerInput containers []*libpod.Container - lastError error restartContainers []*libpod.Container ) @@ -124,15 +121,6 @@ func restartCmd(c *cli.Context) error { logrus.Debugf("Setting maximum workers to %d", maxWorkers) - restartErrors := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) - - for cid, result := range restartErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) + return printParallelOutput(restartErrors, errCount) } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 0fb5345ee..7c0569b78 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -46,9 +45,7 @@ Running containers will not be removed without the -f option. // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cli.Context) error { var ( - delContainers []*libpod.Container - lastError error - deleteFuncs []shared.ParallelWorkerInput + deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() @@ -65,7 +62,13 @@ func rmCmd(c *cli.Context) error { return err } - delContainers, lastError = getAllOrLatestContainers(c, runtime, -1, "all") + delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(delContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } for _, container := range delContainers { con := container @@ -84,14 +87,7 @@ func rmCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - deleteErrors := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - for cid, result := range deleteErrors { - if result != nil { - fmt.Println(result.Error()) - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + // Run the parallel funcs + deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) + return printParallelOutput(deleteErrors, errCount) } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go index 633781a45..e6ce50f95 100644 --- a/cmd/podman/shared/parallel.go +++ b/cmd/podman/shared/parallel.go @@ -30,9 +30,10 @@ func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results // ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker // int determines how many workers/threads should be premade. -func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error { +func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) (map[string]error, int) { var ( - wg sync.WaitGroup + wg sync.WaitGroup + errorCount int ) resultChan := make(chan containerError, len(functions)) @@ -62,9 +63,12 @@ func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map close(resultChan) for ctrError := range resultChan { results[ctrError.ContainerID] = ctrError.Err + if ctrError.Err != nil { + errorCount += 1 + } } - return results + return results, errorCount } // Parallelize provides the maximum number of parallel workers (int) as calculated by a basic diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index cb36fd5cd..04022839a 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -59,7 +59,13 @@ func stopCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) + } var stopFuncs []shared.ParallelWorkerInput for _, ctr := range containers { @@ -85,17 +91,6 @@ func stopCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - stopErrors := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - - for cid, result := range stopErrors { - if result != nil && result != libpod.ErrCtrStopped { - if len(stopErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - return lastError + stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) + return printParallelOutput(stopErrors, errCount) } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 648fc9d3d..d77e056f8 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -37,7 +36,6 @@ var ( func unpauseCmd(c *cli.Context) error { var ( - lastError error unpauseContainers []*libpod.Container unpauseFuncs []shared.ParallelWorkerInput ) @@ -90,18 +88,6 @@ func unpauseCmd(c *cli.Context) error { } logrus.Debugf("Setting maximum workers to %d", maxWorkers) - unpauseErrors := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) - - for cid, result := range unpauseErrors { - if result != nil && result != libpod.ErrCtrStopped { - if len(unpauseErrors) > 1 { - fmt.Println(result.Error()) - } - lastError = result - continue - } - fmt.Println(cid) - } - - return lastError + unpauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) + return printParallelOutput(unpauseErrors, errCount) } diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index afeccb668..5735156c2 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -207,3 +207,20 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error } return pods, lastError } + +//printParallelOutput takes the map of parallel worker results and outputs them +// to stdout +func printParallelOutput(m map[string]error, errCount int) error { + var lastError error + for cid, result := range m { + if result != nil { + if errCount > 1 { + fmt.Println(result.Error()) + } + lastError = result + continue + } + fmt.Println(cid) + } + return lastError +} diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 7474a0d1f..56664a8c1 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -13,7 +13,7 @@ podman\-rm - Remove one or more containers **--force, f** -Force the removal of a running container +Force the removal of a running and paused containers **--all, a** @@ -29,16 +29,29 @@ to run containers such as CRI-O, the last started container could be from either Remove the volumes associated with the container. (Not yet implemented) ## EXAMPLE - +Remove a container by its name *mywebserver* +``` podman rm mywebserver - +``` +Remove several containers by name and container id. +``` podman rm mywebserver myflaskserver 860a4b23 +``` +Forcibly remove a container by container ID. +``` podman rm -f 860a4b23 +``` +Remove all containers regardless of its run state. +``` podman rm -f -a +``` +Forcibly remove the latest container created. +``` podman rm -f --latest +``` ## SEE ALSO podman(1), podman-rmi(1) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 09dc7c48b..09d0ec042 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -246,7 +246,19 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) } if c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + if !force { + return errors.Wrapf(ErrCtrStateInvalid, "container %s is paused, cannot remove until unpaused", c.ID()) + } + if err := c.runtime.ociRuntime.killContainer(c, 9); err != nil { + return err + } + if err := c.unpause(); err != nil { + return err + } + // Need to update container state to make sure we know it's stopped + if err := c.waitForExitFileAndSync(); err != nil { + return err + } } // Check that the container's in a good state to be removed diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index 1a2eb1a09..e80915670 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -91,7 +91,7 @@ var _ = Describe("Podman pause", func() { }) - It("podman remove a paused container by id", func() { + It("podman remove a paused container by id without force", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -111,25 +111,26 @@ var _ = Describe("Podman pause", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) - result = podmanTest.Podman([]string{"rm", "--force", cid}) - result.WaitWithDefaultTimeout() + }) - Expect(result.ExitCode()).To(Equal(125)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) + It("podman remove a paused container by id with force", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() - result = podmanTest.Podman([]string{"unpause", cid}) + result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) result = podmanTest.Podman([]string{"rm", "--force", cid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) - }) It("podman stop a paused container by id", func() { -- cgit v1.2.3-54-g00ecf From b6fabf1d214f7ec0694bd642f879c2afaa037fa7 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Mon, 12 Nov 2018 14:05:15 -0500 Subject: Add space between num & unit in images output Signed-off-by: Qi Wang --- cmd/podman/images.go | 6 ++++-- docs/podman-images.1.md | 44 ++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 24 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/images.go b/cmd/podman/images.go index a8955e49e..c52b26260 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -6,8 +6,7 @@ import ( "sort" "strings" "time" - - "github.com/sirupsen/logrus" + "unicode" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -16,6 +15,7 @@ import ( "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -289,6 +289,8 @@ func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, image sizeStr = err.Error() } else { sizeStr = units.HumanSizeWithPrecision(float64(*size), 3) + lastNumIdx := strings.LastIndexFunc(sizeStr, unicode.IsNumber) + sizeStr = sizeStr[:lastNumIdx+1] + " " + sizeStr[lastNumIdx+1:] } params := imagesTemplateParams{ Repository: repo, diff --git a/docs/podman-images.1.md b/docs/podman-images.1.md index 0bd829a8e..832df0e23 100644 --- a/docs/podman-images.1.md +++ b/docs/podman-images.1.md @@ -49,9 +49,9 @@ Sort by created, id, repository, size or tag (default: created) ``` # podman images REPOSITORY TAG IMAGE ID CREATED SIZE -docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251kB - ebb91b73692b 4 weeks ago 27.2MB -docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126MB +docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251 kB + ebb91b73692b 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126 MB ``` ``` @@ -63,17 +63,17 @@ ebb91b73692b ``` # podman images --noheading -docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251kB - ebb91b73692b 4 weeks ago 27.2MB -docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126MB +docker.io/kubernetes/pause latest e3d42bcaf643 3 years ago 251 kB + ebb91b73692b 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest 4526339ae51c 6 weeks ago 126 MB ``` ``` # podman images --no-trunc REPOSITORY TAG IMAGE ID CREATED SIZE -docker.io/kubernetes/pause latest sha256:e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27 3 years ago 251kB - sha256:ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9 4 weeks ago 27.2MB -docker.io/library/ubuntu latest sha256:4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a 6 weeks ago 126MB +docker.io/kubernetes/pause latest sha256:e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27 3 years ago 251 kB + sha256:ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9 4 weeks ago 27.2 MB +docker.io/library/ubuntu latest sha256:4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a 6 weeks ago 126 MB ``` ``` @@ -87,7 +87,7 @@ ebb91b73692b ``` # podman images --filter dangling=true REPOSITORY TAG IMAGE ID CREATED SIZE - ebb91b73692b 4 weeks ago 27.2MB + ebb91b73692b 4 weeks ago 27.2 MB ``` ``` @@ -126,25 +126,25 @@ REPOSITORY TAG IMAGE ID CREATED SIZE ``` # podman images --sort repository REPOSITORY TAG IMAGE ID CREATED SIZE - 2460217d76fc About a minute ago 4.41MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB -localhost/myapp latest b2e0ad03474a About a minute ago 4.41MB -registry.access.redhat.com/rhel7 latest 7a840db7f020 2 weeks ago 211MB -registry.fedoraproject.org/fedora 27 801894bc0e43 6 weeks ago 246MB + 2460217d76fc About a minute ago 4.41 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB +localhost/myapp latest b2e0ad03474a About a minute ago 4.41 MB +registry.access.redhat.com/rhel7 latest 7a840db7f020 2 weeks ago 211 MB +registry.fedoraproject.org/fedora 27 801894bc0e43 6 weeks ago 246 MB ``` ``` # podman images REPOSITORY TAG IMAGE ID CREATED SIZE -localhost/test latest 18f0c080cd72 4 seconds ago 4.42MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB +localhost/test latest 18f0c080cd72 4 seconds ago 4.42 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB # podman images -a REPOSITORY TAG IMAGE ID CREATED SIZE -localhost/test latest 18f0c080cd72 6 seconds ago 4.42MB - 270e70dc54c0 7 seconds ago 4.42MB - 4ed6fbe43414 8 seconds ago 4.41MB - 6b0df8e71508 8 seconds ago 4.41MB -docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41MB +localhost/test latest 18f0c080cd72 6 seconds ago 4.42 MB + 270e70dc54c0 7 seconds ago 4.42 MB + 4ed6fbe43414 8 seconds ago 4.41 MB + 6b0df8e71508 8 seconds ago 4.41 MB +docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB ``` ## SEE ALSO -- cgit v1.2.3-54-g00ecf From bb6c1cf8d1667c7c8e4d539ea2250a18fa89a58a Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 8 Nov 2018 06:12:14 -0500 Subject: libpod should know if the network is disabled /etc/resolv.conf and /etc/hosts should not be created and mounted when the network is disabled. We should not be calling the network setup and cleanup functions when it is disabled either. In doing this patch, I found that all of the bind mounts were particular to Linux along with the generate functions, so I moved them to container_internal_linux.go Since we are checking if we are using a network namespace, we need to check after the network namespaces has been created in the spec. Signed-off-by: Daniel J Walsh --- cmd/podman/create.go | 5 + libpod/container.go | 12 ++ libpod/container_internal.go | 228 +---------------------------------- libpod/container_internal_linux.go | 235 +++++++++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 227 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 9f6825c95..bcf830c7c 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -670,6 +670,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") } + if !netMode.IsPrivate() { + if c.IsSet("dns-search") || c.IsSet("dns") || c.IsSet("dns-opt") { + return nil, errors.Errorf("specifying DNS flags when network mode is shared with the host or another container is not allowed") + } + } // Validate domains are good for _, dom := range c.StringSlice("dns-search") { diff --git a/libpod/container.go b/libpod/container.go index 7bb5b2687..16f61d021 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -996,3 +996,15 @@ func (c *Container) IsInfra() bool { func (c *Container) IsReadOnly() bool { return c.config.Spec.Root.Readonly } + +// NetworkDisabled returns whether the container is running with a disabled network +func (c *Container) NetworkDisabled() bool { + if !c.config.PostConfigureNetNS { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + return ns.Path == "" + } + } + } + return false +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index d2f48d661..051e0aeb7 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -19,14 +19,11 @@ import ( "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" "github.com/containers/libpod/pkg/lookup" - "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/mount" - "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -583,7 +580,7 @@ func (c *Container) checkDependenciesRunningLocked(depCtrs map[string]*Container } func (c *Container) completeNetworkSetup() error { - if !c.config.PostConfigureNetNS { + if !c.config.PostConfigureNetNS || c.NetworkDisabled() { return nil } if err := c.syncContainer(); err != nil { @@ -597,10 +594,6 @@ func (c *Container) completeNetworkSetup() error { // Initialize a container, creating it in the runtime func (c *Container) init(ctx context.Context) error { - if err := c.makeBindMounts(); err != nil { - return err - } - // Generate the OCI spec spec, err := c.generateSpec(ctx) if err != nil { @@ -987,86 +980,6 @@ func (c *Container) postDeleteHooks(ctx context.Context) (err error) { return nil } -// Make standard bind mounts to include in the container -func (c *Container) makeBindMounts() error { - if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { - return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) - } - - if c.state.BindMounts == nil { - c.state.BindMounts = make(map[string]string) - } - - // SHM is always added when we mount the container - c.state.BindMounts["/dev/shm"] = c.config.ShmDir - - // Make /etc/resolv.conf - if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/resolv.conf") - } - newResolv, err := c.generateResolvConf() - if err != nil { - return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) - } - c.state.BindMounts["/etc/resolv.conf"] = newResolv - - newPasswd, err := c.generatePasswd() - if err != nil { - return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) - } - if newPasswd != "" { - // Make /etc/passwd - if _, ok := c.state.BindMounts["/etc/passwd"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/passwd") - } - logrus.Debugf("adding entry to /etc/passwd for non existent default user") - c.state.BindMounts["/etc/passwd"] = newPasswd - } - // Make /etc/hosts - if _, ok := c.state.BindMounts["/etc/hosts"]; ok { - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/hosts") - } - newHosts, err := c.generateHosts() - if err != nil { - return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) - } - c.state.BindMounts["/etc/hosts"] = newHosts - - // Make /etc/hostname - // This should never change, so no need to recreate if it exists - if _, ok := c.state.BindMounts["/etc/hostname"]; !ok { - hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname()) - if err != nil { - return errors.Wrapf(err, "error creating hostname file for container %s", c.ID()) - } - c.state.BindMounts["/etc/hostname"] = hostnamePath - } - - // Make .containerenv - // Empty file, so no need to recreate if it exists - if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok { - // Empty string for now, but we may consider populating this later - containerenvPath, err := c.writeStringToRundir(".containerenv", "") - if err != nil { - return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID()) - } - c.state.BindMounts["/run/.containerenv"] = containerenvPath - } - - // Add Secret Mounts - secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID()) - for _, mount := range secretMounts { - if _, ok := c.state.BindMounts[mount.Destination]; !ok { - c.state.BindMounts[mount.Destination] = mount.Source - } - } - - return nil -} - // writeStringToRundir copies the provided file to the runtimedir func (c *Container) writeStringToRundir(destFile, output string) (string, error) { destFileName := filepath.Join(c.state.RunDir, destFile) @@ -1095,145 +1008,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return filepath.Join(c.state.DestinationRunDir, destFile), nil } -// generatePasswd generates a container specific passwd file, -// iff g.config.User is a number -func (c *Container) generatePasswd() (string, error) { - var ( - groupspec string - group *user.Group - gid int - ) - if c.config.User == "" { - return "", nil - } - spec := strings.SplitN(c.config.User, ":", 2) - userspec := spec[0] - if len(spec) > 1 { - groupspec = spec[1] - } - // If a non numeric User, then don't generate passwd - uid, err := strconv.ParseUint(userspec, 10, 32) - if err != nil { - return "", nil - } - // Lookup the user to see if it exists in the container image - _, err = lookup.GetUser(c.state.Mountpoint, userspec) - if err != nil && err != user.ErrNoPasswdEntries { - return "", err - } - if err == nil { - return "", nil - } - if groupspec != "" { - if !c.state.Mounted { - return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID()) - } - group, err = lookup.GetGroup(c.state.Mountpoint, groupspec) - if err != nil { - if err == user.ErrNoGroupEntries { - return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec) - } - return "", err - } - gid = group.Gid - } - originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") - orig, err := ioutil.ReadFile(originPasswdFile) - if err != nil && !os.IsNotExist(err) { - return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) - } - - pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) - passwdFile, err := c.writeStringToRundir("passwd", pwd) - if err != nil { - return "", errors.Wrapf(err, "failed to create temporary passwd file") - } - if os.Chmod(passwdFile, 0644); err != nil { - return "", err - } - return passwdFile, nil -} - -// generateResolvConf generates a containers resolv.conf -func (c *Container) generateResolvConf() (string, error) { - // Determine the endpoint for resolv.conf in case it is a symlink - resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf") - if err != nil { - return "", err - } - - contents, err := ioutil.ReadFile(resolvPath) - if err != nil { - return "", errors.Wrapf(err, "unable to read %s", resolvPath) - } - - // Process the file to remove localhost nameservers - // TODO: set ipv6 enable bool more sanely - resolv, err := resolvconf.FilterResolvDNS(contents, true) - if err != nil { - return "", errors.Wrapf(err, "error parsing host resolv.conf") - } - - // Make a new resolv.conf - nameservers := resolvconf.GetNameservers(resolv.Content) - if len(c.config.DNSServer) > 0 { - // We store DNS servers as net.IP, so need to convert to string - nameservers = []string{} - for _, server := range c.config.DNSServer { - nameservers = append(nameservers, server.String()) - } - } - - search := resolvconf.GetSearchDomains(resolv.Content) - if len(c.config.DNSSearch) > 0 { - search = c.config.DNSSearch - } - - options := resolvconf.GetOptions(resolv.Content) - if len(c.config.DNSOption) > 0 { - options = c.config.DNSOption - } - - destPath := filepath.Join(c.state.RunDir, "resolv.conf") - - if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID()) - } - - // Build resolv.conf - if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { - return "", errors.Wrapf(err, "error building resolv.conf for container %s") - } - - // Relabel resolv.conf for the container - if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil { - return "", err - } - - return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil -} - -// generateHosts creates a containers hosts file -func (c *Container) generateHosts() (string, error) { - orig, err := ioutil.ReadFile("/etc/hosts") - if err != nil { - return "", errors.Wrapf(err, "unable to read /etc/hosts") - } - hosts := string(orig) - if len(c.config.HostAdd) > 0 { - for _, host := range c.config.HostAdd { - // the host format has already been verified at this point - fields := strings.SplitN(host, ":", 2) - hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) - } - } - if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 { - ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0] - hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname()) - } - return c.writeStringToRundir("hosts", hosts) -} - func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error { var uid, gid int mountPoint := c.state.Mountpoint diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 66c7e8a04..86e3db4e3 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "sync" "syscall" @@ -21,8 +22,11 @@ import ( crioAnnotations "github.com/containers/libpod/pkg/annotations" "github.com/containers/libpod/pkg/criu" "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage/pkg/idtools" + "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -132,6 +136,9 @@ func (c *Container) prepare() (err error) { // cleanupNetwork unmounts and cleans up the container's network func (c *Container) cleanupNetwork() error { + if c.NetworkDisabled() { + return nil + } if c.state.NetNS == nil { logrus.Debugf("Network is already cleaned up, skipping...") return nil @@ -169,6 +176,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path()) } } + + if err := c.makeBindMounts(); err != nil { + return nil, err + } + // Check if the spec file mounts contain the label Relabel flags z or Z. // If they do, relabel the source directory and then remove the option. for _, m := range g.Mounts() { @@ -591,3 +603,226 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { return c.save() } + +// Make standard bind mounts to include in the container +func (c *Container) makeBindMounts() error { + if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { + return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) + } + + if c.state.BindMounts == nil { + c.state.BindMounts = make(map[string]string) + } + + if !c.NetworkDisabled() { + // Make /etc/resolv.conf + if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/resolv.conf") + } + newResolv, err := c.generateResolvConf() + if err != nil { + return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID()) + } + c.state.BindMounts["/etc/resolv.conf"] = newResolv + + // Make /etc/hosts + if _, ok := c.state.BindMounts["/etc/hosts"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/hosts") + } + newHosts, err := c.generateHosts() + if err != nil { + return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) + } + c.state.BindMounts["/etc/hosts"] = newHosts + + } + + // SHM is always added when we mount the container + c.state.BindMounts["/dev/shm"] = c.config.ShmDir + + newPasswd, err := c.generatePasswd() + if err != nil { + return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) + } + if newPasswd != "" { + // Make /etc/passwd + if _, ok := c.state.BindMounts["/etc/passwd"]; ok { + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/passwd") + } + logrus.Debugf("adding entry to /etc/passwd for non existent default user") + c.state.BindMounts["/etc/passwd"] = newPasswd + } + + // Make /etc/hostname + // This should never change, so no need to recreate if it exists + if _, ok := c.state.BindMounts["/etc/hostname"]; !ok { + hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname()) + if err != nil { + return errors.Wrapf(err, "error creating hostname file for container %s", c.ID()) + } + c.state.BindMounts["/etc/hostname"] = hostnamePath + } + + // Make .containerenv + // Empty file, so no need to recreate if it exists + if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok { + // Empty string for now, but we may consider populating this later + containerenvPath, err := c.writeStringToRundir(".containerenv", "") + if err != nil { + return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID()) + } + c.state.BindMounts["/run/.containerenv"] = containerenvPath + } + + // Add Secret Mounts + secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID()) + for _, mount := range secretMounts { + if _, ok := c.state.BindMounts[mount.Destination]; !ok { + c.state.BindMounts[mount.Destination] = mount.Source + } + } + + return nil +} + +// generateResolvConf generates a containers resolv.conf +func (c *Container) generateResolvConf() (string, error) { + // Determine the endpoint for resolv.conf in case it is a symlink + resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf") + if err != nil { + return "", err + } + + contents, err := ioutil.ReadFile(resolvPath) + if err != nil { + return "", errors.Wrapf(err, "unable to read %s", resolvPath) + } + + // Process the file to remove localhost nameservers + // TODO: set ipv6 enable bool more sanely + resolv, err := resolvconf.FilterResolvDNS(contents, true) + if err != nil { + return "", errors.Wrapf(err, "error parsing host resolv.conf") + } + + // Make a new resolv.conf + nameservers := resolvconf.GetNameservers(resolv.Content) + if len(c.config.DNSServer) > 0 { + // We store DNS servers as net.IP, so need to convert to string + nameservers = []string{} + for _, server := range c.config.DNSServer { + nameservers = append(nameservers, server.String()) + } + } + + search := resolvconf.GetSearchDomains(resolv.Content) + if len(c.config.DNSSearch) > 0 { + search = c.config.DNSSearch + } + + options := resolvconf.GetOptions(resolv.Content) + if len(c.config.DNSOption) > 0 { + options = c.config.DNSOption + } + + destPath := filepath.Join(c.state.RunDir, "resolv.conf") + + if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { + return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID()) + } + + // Build resolv.conf + if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { + return "", errors.Wrapf(err, "error building resolv.conf for container %s") + } + + // Relabel resolv.conf for the container + if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil { + return "", err + } + + return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil +} + +// generateHosts creates a containers hosts file +func (c *Container) generateHosts() (string, error) { + orig, err := ioutil.ReadFile("/etc/hosts") + if err != nil { + return "", errors.Wrapf(err, "unable to read /etc/hosts") + } + hosts := string(orig) + if len(c.config.HostAdd) > 0 { + for _, host := range c.config.HostAdd { + // the host format has already been verified at this point + fields := strings.SplitN(host, ":", 2) + hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) + } + } + if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 { + ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0] + hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname()) + } + return c.writeStringToRundir("hosts", hosts) +} + +// generatePasswd generates a container specific passwd file, +// iff g.config.User is a number +func (c *Container) generatePasswd() (string, error) { + var ( + groupspec string + group *user.Group + gid int + ) + if c.config.User == "" { + return "", nil + } + spec := strings.SplitN(c.config.User, ":", 2) + userspec := spec[0] + if len(spec) > 1 { + groupspec = spec[1] + } + // If a non numeric User, then don't generate passwd + uid, err := strconv.ParseUint(userspec, 10, 32) + if err != nil { + return "", nil + } + // Lookup the user to see if it exists in the container image + _, err = lookup.GetUser(c.state.Mountpoint, userspec) + if err != nil && err != user.ErrNoPasswdEntries { + return "", err + } + if err == nil { + return "", nil + } + if groupspec != "" { + if !c.state.Mounted { + return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID()) + } + group, err = lookup.GetGroup(c.state.Mountpoint, groupspec) + if err != nil { + if err == user.ErrNoGroupEntries { + return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec) + } + return "", err + } + gid = group.Gid + } + originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") + orig, err := ioutil.ReadFile(originPasswdFile) + if err != nil && !os.IsNotExist(err) { + return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) + } + + pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) + passwdFile, err := c.writeStringToRundir("passwd", pwd) + if err != nil { + return "", errors.Wrapf(err, "failed to create temporary passwd file") + } + if os.Chmod(passwdFile, 0644); err != nil { + return "", err + } + return passwdFile, nil +} -- cgit v1.2.3-54-g00ecf From 780b790415f94ea41ef70bd97828ecb92a4f06d1 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 16 Nov 2018 16:05:07 +0100 Subject: runlabel: use shlex for splitting commands Use github.com/google/shlex for splitting commands instead of splitting at whitespaces. This way, we avoid accidentally splitting single string arguments into mutliple ones. Signed-off-by: Valentin Rothberg --- cmd/podman/shared/funcs.go | 8 +- cmd/podman/shared/funcs_test.go | 9 +- vendor.conf | 1 + vendor/github.com/google/shlex/COPYING | 202 ++++++++++++++++ vendor/github.com/google/shlex/README | 2 + vendor/github.com/google/shlex/shlex.go | 416 ++++++++++++++++++++++++++++++++ 6 files changed, 633 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/google/shlex/COPYING create mode 100644 vendor/github.com/google/shlex/README create mode 100644 vendor/github.com/google/shlex/shlex.go (limited to 'cmd') diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index a92e0d547..8520c0616 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/google/shlex" ) func substituteCommand(cmd string) (string, error) { @@ -42,7 +44,11 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { if name == "" { name = imageName } - cmd := strings.Split(command, " ") + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } prog, err := substituteCommand(cmd[0]) if err != nil { diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go index 596df84e8..7506b9d9c 100644 --- a/cmd/podman/shared/funcs_test.go +++ b/cmd/podman/shared/funcs_test.go @@ -18,10 +18,11 @@ var ( ) func TestGenerateCommand(t *testing.T) { - inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" + inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\"" + correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world" newCommand, err := GenerateCommand(inputCommand, "foo", "bar") assert.Nil(t, err) + assert.Equal(t, "hello world", newCommand[11]) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } @@ -108,8 +109,8 @@ func TestGenerateCommandNoSetName(t *testing.T) { } func TestGenerateCommandNoName(t *testing.T) { - inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" - correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" + inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" + correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" newCommand, err := GenerateCommand(inputCommand, "foo", "") assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) diff --git a/vendor.conf b/vendor.conf index a16975bf9..c8e968648 100644 --- a/vendor.conf +++ b/vendor.conf @@ -99,3 +99,4 @@ github.com/openshift/imagebuilder master github.com/ulikunitz/xz v0.5.4 github.com/mailru/easyjson 03f2033d19d5860aef995fe360ac7d395cd8ce65 github.com/coreos/go-iptables 25d087f3cffd9aedc0c2b7eff25f23cbf3c20fe1 +github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe diff --git a/vendor/github.com/google/shlex/COPYING b/vendor/github.com/google/shlex/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/google/shlex/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/google/shlex/README b/vendor/github.com/google/shlex/README new file mode 100644 index 000000000..c86bcc066 --- /dev/null +++ b/vendor/github.com/google/shlex/README @@ -0,0 +1,2 @@ +go-shlex is a simple lexer for go that supports shell-style quoting, +commenting, and escaping. diff --git a/vendor/github.com/google/shlex/shlex.go b/vendor/github.com/google/shlex/shlex.go new file mode 100644 index 000000000..d98308bce --- /dev/null +++ b/vendor/github.com/google/shlex/shlex.go @@ -0,0 +1,416 @@ +/* +Copyright 2012 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package shlex implements a simple lexer which splits input in to tokens using +shell-style rules for quoting and commenting. + +The basic use case uses the default ASCII lexer to split a string into sub-strings: + + shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"} + +To process a stream of strings: + + l := NewLexer(os.Stdin) + for ; token, err := l.Next(); err != nil { + // process token + } + +To access the raw token stream (which includes tokens for comments): + + t := NewTokenizer(os.Stdin) + for ; token, err := t.Next(); err != nil { + // process token + } + +*/ +package shlex + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// TokenType is a top-level token classification: A word, space, comment, unknown. +type TokenType int + +// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape. +type runeTokenClass int + +// the internal state used by the lexer state machine +type lexerState int + +// Token is a (type, value) pair representing a lexographical token. +type Token struct { + tokenType TokenType + value string +} + +// Equal reports whether tokens a, and b, are equal. +// Two tokens are equal if both their types and values are equal. A nil token can +// never be equal to another token. +func (a *Token) Equal(b *Token) bool { + if a == nil || b == nil { + return false + } + if a.tokenType != b.tokenType { + return false + } + return a.value == b.value +} + +// Named classes of UTF-8 runes +const ( + spaceRunes = " \t\r\n" + escapingQuoteRunes = `"` + nonEscapingQuoteRunes = "'" + escapeRunes = `\` + commentRunes = "#" +) + +// Classes of rune token +const ( + unknownRuneClass runeTokenClass = iota + spaceRuneClass + escapingQuoteRuneClass + nonEscapingQuoteRuneClass + escapeRuneClass + commentRuneClass + eofRuneClass +) + +// Classes of lexographic token +const ( + UnknownToken TokenType = iota + WordToken + SpaceToken + CommentToken +) + +// Lexer state machine states +const ( + startState lexerState = iota // no runes have been seen + inWordState // processing regular runes in a word + escapingState // we have just consumed an escape rune; the next rune is literal + escapingQuotedState // we have just consumed an escape rune within a quoted string + quotingEscapingState // we are within a quoted string that supports escaping ("...") + quotingState // we are within a string that does not support escaping ('...') + commentState // we are within a comment (everything following an unquoted or unescaped # +) + +// tokenClassifier is used for classifying rune characters. +type tokenClassifier map[rune]runeTokenClass + +func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) { + for _, runeChar := range runes { + typeMap[runeChar] = tokenType + } +} + +// newDefaultClassifier creates a new classifier for ASCII characters. +func newDefaultClassifier() tokenClassifier { + t := tokenClassifier{} + t.addRuneClass(spaceRunes, spaceRuneClass) + t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass) + t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass) + t.addRuneClass(escapeRunes, escapeRuneClass) + t.addRuneClass(commentRunes, commentRuneClass) + return t +} + +// ClassifyRune classifiees a rune +func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass { + return t[runeVal] +} + +// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped. +type Lexer Tokenizer + +// NewLexer creates a new lexer from an input stream. +func NewLexer(r io.Reader) *Lexer { + + return (*Lexer)(NewTokenizer(r)) +} + +// Next returns the next word, or an error. If there are no more words, +// the error will be io.EOF. +func (l *Lexer) Next() (string, error) { + for { + token, err := (*Tokenizer)(l).Next() + if err != nil { + return "", err + } + switch token.tokenType { + case WordToken: + return token.value, nil + case CommentToken: + // skip comments + default: + return "", fmt.Errorf("Unknown token type: %v", token.tokenType) + } + } +} + +// Tokenizer turns an input stream into a sequence of typed tokens +type Tokenizer struct { + input bufio.Reader + classifier tokenClassifier +} + +// NewTokenizer creates a new tokenizer from an input stream. +func NewTokenizer(r io.Reader) *Tokenizer { + input := bufio.NewReader(r) + classifier := newDefaultClassifier() + return &Tokenizer{ + input: *input, + classifier: classifier} +} + +// scanStream scans the stream for the next token using the internal state machine. +// It will panic if it encounters a rune which it does not know how to handle. +func (t *Tokenizer) scanStream() (*Token, error) { + state := startState + var tokenType TokenType + var value []rune + var nextRune rune + var nextRuneType runeTokenClass + var err error + + for { + nextRune, _, err = t.input.ReadRune() + nextRuneType = t.classifier.ClassifyRune(nextRune) + + if err == io.EOF { + nextRuneType = eofRuneClass + err = nil + } else if err != nil { + return nil, err + } + + switch state { + case startState: // no runes read yet + { + switch nextRuneType { + case eofRuneClass: + { + return nil, io.EOF + } + case spaceRuneClass: + { + } + case escapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingState + } + case escapeRuneClass: + { + tokenType = WordToken + state = escapingState + } + case commentRuneClass: + { + tokenType = CommentToken + state = commentState + } + default: + { + tokenType = WordToken + value = append(value, nextRune) + state = inWordState + } + } + } + case inWordState: // in a regular word + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + state = quotingState + } + case escapeRuneClass: + { + state = escapingState + } + default: + { + value = append(value, nextRune) + } + } + } + case escapingState: // the rune after an escape character + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = inWordState + value = append(value, nextRune) + } + } + } + case escapingQuotedState: // the next rune after an escape character, in double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = quotingEscapingState + value = append(value, nextRune) + } + } + } + case quotingEscapingState: // in escaping double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = inWordState + } + case escapeRuneClass: + { + state = escapingQuotedState + } + default: + { + value = append(value, nextRune) + } + } + } + case quotingState: // in non-escaping single quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case nonEscapingQuoteRuneClass: + { + state = inWordState + } + default: + { + value = append(value, nextRune) + } + } + } + case commentState: // in a comment + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + if nextRune == '\n' { + state = startState + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } else { + value = append(value, nextRune) + } + } + default: + { + value = append(value, nextRune) + } + } + } + default: + { + return nil, fmt.Errorf("Unexpected state: %v", state) + } + } + } +} + +// Next returns the next token in the stream. +func (t *Tokenizer) Next() (*Token, error) { + return t.scanStream() +} + +// Split partitions a string into a slice of strings. +func Split(s string) ([]string, error) { + l := NewLexer(strings.NewReader(s)) + subStrings := make([]string, 0) + for { + word, err := l.Next() + if err != nil { + if err == io.EOF { + return subStrings, nil + } + return subStrings, err + } + subStrings = append(subStrings, word) + } +} -- cgit v1.2.3-54-g00ecf From f11a74e7150c2929a0ed18732ee0c4895f7e75be Mon Sep 17 00:00:00 2001 From: baude Date: Tue, 13 Nov 2018 10:53:18 -0600 Subject: output libpod container to kubernetes yaml scope out new kube subcommand where we can add generate. you can now generate kubernetes YAML that will allow you to run the container in a kubernetes environment. When The YAML description will always "wrap" a container in a simple v1.Pod description. Tests and further documentation will be added in additional PRs. This function should be considered very much "under heavy development" at this point. Signed-off-by: baude --- cmd/podman/kube.go | 22 ++++ cmd/podman/kube_generate.go | 93 +++++++++++++++ cmd/podman/main.go | 1 + libpod/kube.go | 270 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 386 insertions(+) create mode 100644 cmd/podman/kube.go create mode 100644 cmd/podman/kube_generate.go create mode 100644 libpod/kube.go (limited to 'cmd') diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go new file mode 100644 index 000000000..ced87e2bd --- /dev/null +++ b/cmd/podman/kube.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + kubeSubCommands = []cli.Command{ + containerKubeCommand, + } + + kubeDescription = "Work with Kubernetes objects" + kubeCommand = cli.Command{ + Name: "kube", + Usage: "Import and export Kubernetes objections from and to Podman", + Description: containerDescription, + ArgsUsage: "", + Subcommands: kubeSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) diff --git a/cmd/podman/kube_generate.go b/cmd/podman/kube_generate.go new file mode 100644 index 000000000..a18912668 --- /dev/null +++ b/cmd/podman/kube_generate.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + containerKubeFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "service, s", + Usage: "only generate YAML for kubernetes service object", + }, + LatestFlag, + } + containerKubeDescription = "Generate Kubernetes Pod YAML" + containerKubeCommand = cli.Command{ + Name: "generate", + Usage: "Generate Kubernetes pod YAML for a container", + Description: containerKubeDescription, + Flags: sortFlags(containerKubeFlags), + Action: generateKubeYAMLCmd, + ArgsUsage: "CONTAINER-NAME", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +// generateKubeYAMLCmdgenerates or replays kube +func generateKubeYAMLCmd(c *cli.Context) error { + var ( + container *libpod.Container + err error + output []byte + ) + + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { + return errors.Errorf("you must provide one container ID or name or --latest") + } + if c.Bool("service") { + return errors.Wrapf(libpod.ErrNotImplemented, "service generation") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + // Get the container in question + if c.Bool("latest") { + container, err = runtime.GetLatestContainer() + } else { + container, err = runtime.LookupContainer(args[0]) + } + if err != nil { + return err + } + + if len(container.Dependencies()) > 0 { + return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + } + + podYAML, err := container.InspectForKube() + if err != nil { + return err + } + + developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") + logrus.Warn("This function is still under heavy development.") + // Marshall the results + b, err := yaml.Marshal(podYAML) + if err != nil { + return err + } + output = append(output, developmentComment...) + output = append(output, b...) + // Output the v1.Pod with the v1.Container + fmt.Println(string(output)) + + return nil +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 38eac4504..6be192593 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -77,6 +77,7 @@ func main() { infoCommand, inspectCommand, killCommand, + kubeCommand, loadCommand, loginCommand, logoutCommand, diff --git a/libpod/kube.go b/libpod/kube.go new file mode 100644 index 000000000..00db0033b --- /dev/null +++ b/libpod/kube.go @@ -0,0 +1,270 @@ +package libpod + +import ( + "fmt" + "strings" + + "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/util" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// InspectForKube takes a slice of libpod containers and generates +// one v1.Pod description that includes just a single container. +func (c *Container) InspectForKube() (*v1.Pod, error) { + // Generate the v1.Pod yaml description + return simplePodWithV1Container(c) +} + +// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated +// for a single container. we "insert" that container description in a pod. +func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) { + var containers []v1.Container + result, err := containerToV1Container(ctr) + if err != nil { + return nil, err + } + containers = append(containers, result) + + tm := v12.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + } + + // Add a label called "app" with the containers name as a value + labels := make(map[string]string) + labels["app"] = removeUnderscores(ctr.Name()) + om := v12.ObjectMeta{ + // The name of the pod is container_name-libpod + Name: fmt.Sprintf("%s-libpod", removeUnderscores(ctr.Name())), + Labels: labels, + // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp + // will reflect time this is run (not container create time) because the conversion + // of the container create time to v1 Time is probably not warranted nor worthwhile. + CreationTimestamp: v12.Now(), + } + ps := v1.PodSpec{ + Containers: containers, + } + p := v1.Pod{ + TypeMeta: tm, + ObjectMeta: om, + Spec: ps, + } + return &p, nil +} + +// containerToV1Container converts information we know about a libpod container +// to a V1.Container specification. +func containerToV1Container(c *Container) (v1.Container, error) { + kubeContainer := v1.Container{} + kubeSec, err := generateKubeSecurityContext(c) + if err != nil { + return kubeContainer, err + } + + if len(c.config.Spec.Linux.Devices) > 0 { + // TODO Enable when we can support devices and their names + devices, err := generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeDevices = devices + return kubeContainer, errors.Wrapf(ErrNotImplemented, "linux devices") + } + + if len(c.config.UserVolumes) > 0 { + // TODO When we until we can resolve what the volume name should be, this is disabled + // Volume names need to be coordinated "globally" in the kube files. + volumes, err := libpodMountsToKubeVolumeMounts(c) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeMounts = volumes + return kubeContainer, errors.Wrapf(ErrNotImplemented, "volume names") + } + + envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env) + if err != nil { + return kubeContainer, nil + } + + ports, err := ocicniPortMappingToContainerPort(c.PortMappings()) + if err != nil { + return kubeContainer, nil + } + + containerCommands := c.Command() + kubeContainer.Name = removeUnderscores(c.Name()) + + _, image := c.Image() + kubeContainer.Image = image + kubeContainer.Stdin = c.Stdin() + kubeContainer.Command = containerCommands + // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint. + // right now we just take the container's command + //container.Args = args + kubeContainer.WorkingDir = c.WorkingDir() + kubeContainer.Ports = ports + // This should not be applicable + //container.EnvFromSource = + kubeContainer.Env = envVariables + // TODO enable resources when we can support naming conventions + //container.Resources + kubeContainer.SecurityContext = kubeSec + kubeContainer.StdinOnce = false + kubeContainer.TTY = c.config.Spec.Process.Terminal + + return kubeContainer, nil +} + +// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts +// it to a v1.ContainerPort format for kube output +func ocicniPortMappingToContainerPort(portMappings []ocicni.PortMapping) ([]v1.ContainerPort, error) { + var containerPorts []v1.ContainerPort + for _, p := range portMappings { + var protocol v1.Protocol + switch strings.ToUpper(p.Protocol) { + case "TCP": + protocol = v1.ProtocolTCP + case "UDP": + protocol = v1.ProtocolUDP + default: + return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol) + } + cp := v1.ContainerPort{ + // Name will not be supported + HostPort: p.HostPort, + HostIP: p.HostIP, + ContainerPort: p.ContainerPort, + Protocol: protocol, + } + containerPorts = append(containerPorts, cp) + } + return containerPorts, nil +} + +// libpodEnvVarsToKubeEnvVars converts a key=value string slice to []v1.EnvVar +func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) { + var envVars []v1.EnvVar + for _, e := range envs { + splitE := strings.SplitN(e, "=", 2) + if len(splitE) != 2 { + return envVars, errors.Errorf("environment variable %s is malformed; should be key=value", e) + } + ev := v1.EnvVar{ + Name: splitE[0], + Value: splitE[1], + } + envVars = append(envVars, ev) + } + return envVars, nil +} + +// Is this worth it? +func libpodMaxAndMinToResourceList(c *Container) (v1.ResourceList, v1.ResourceList) { //nolint + // It does not appear we can properly calculate CPU resources from the information + // we know in libpod. Libpod knows CPUs by time, shares, etc. + + // We also only know about a memory limit; no memory minimum + maxResources := make(map[v1.ResourceName]resource.Quantity) + minResources := make(map[v1.ResourceName]resource.Quantity) + config := c.Config() + maxMem := config.Spec.Linux.Resources.Memory.Limit + + _ = maxMem + + return maxResources, minResources +} + +func generateKubeVolumeMount(hostSourcePath string, mounts []specs.Mount) (v1.VolumeMount, error) { + vm := v1.VolumeMount{} + for _, m := range mounts { + if m.Source == hostSourcePath { + // TODO Name is not provided and is required by Kube; therefore, this is disabled earlier + //vm.Name = + vm.MountPath = m.Source + vm.SubPath = m.Destination + if util.StringInSlice("ro", m.Options) { + vm.ReadOnly = true + } + return vm, nil + } + } + return vm, errors.New("unable to find mount source") +} + +// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands +func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, error) { + // At this point, I dont think we can distinguish between the default + // volume mounts and user added ones. For now, we pass them all. + var vms []v1.VolumeMount + for _, hostSourcePath := range c.config.UserVolumes { + vm, err := generateKubeVolumeMount(hostSourcePath, c.config.Spec.Mounts) + if err != nil { + return vms, err + } + vms = append(vms, vm) + } + return vms, nil +} + +// generateKubeSecurityContext generates a securityContext based on the existing container +func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { + priv := c.Privileged() + ro := c.IsReadOnly() + allowPrivEscalation := !c.Spec().Process.NoNewPrivileges + + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //caps := v1.Capabilities{ + // //Add: c.config.Spec.Process.Capabilities + //} + sc := v1.SecurityContext{ + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //Capabilities: &caps, + Privileged: &priv, + // TODO How do we know if selinux were passed into podman + //SELinuxOptions: + // RunAsNonRoot is an optional parameter; our first implementations should be root only; however + // I'm leaving this as a bread-crumb for later + //RunAsNonRoot: &nonRoot, + ReadOnlyRootFilesystem: &ro, + AllowPrivilegeEscalation: &allowPrivEscalation, + } + + if c.User() != "" { + // It is *possible* that + logrus.Debug("Looking in container for user: %s", c.User()) + u, err := lookup.GetUser(c.state.Mountpoint, c.User()) + if err != nil { + return nil, err + } + user := int64(u.Uid) + sc.RunAsUser = &user + } + return &sc, nil +} + +// generateKubeVolumeDeviceFromLinuxDevice takes a list of devices and makes a VolumeDevice struct for kube +func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) ([]v1.VolumeDevice, error) { + var volumeDevices []v1.VolumeDevice + for _, d := range devices { + vd := v1.VolumeDevice{ + // TBD How are we going to sync up these names + //Name: + DevicePath: d.Path, + } + volumeDevices = append(volumeDevices, vd) + } + return volumeDevices, nil +} + +func removeUnderscores(s string) string { + return strings.Replace(s, "_", "", -1) +} -- cgit v1.2.3-54-g00ecf From 690c52a113124efcedccb84e44198e7602f064ec Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 19 Nov 2018 13:20:56 -0600 Subject: Allow users to expose ports from the pod to the host we need to allow users to expose ports to the host for the purposes of networking, like a webserver. the port exposure must be done at the time the pod is created. strictly speaking, the port exposure occurs on the infra container. Signed-off-by: baude --- cmd/podman/pod_create.go | 59 ++++++++++++++++++ completions/bash/podman | 6 +- docs/podman-pod-create.1.md | 9 +++ libpod/options.go | 11 ++++ libpod/pod.go | 4 +- libpod/pod_easyjson.go | 128 ++++++++++++++++++++++++++++++++++++++ libpod/runtime_pod_infra_linux.go | 4 +- pkg/spec/createconfig.go | 1 - test/e2e/pod_create_test.go | 39 ++++++++++++ 9 files changed, 254 insertions(+), 7 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 63fa6b294..a3364ac4b 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,11 +3,15 @@ package main import ( "fmt" "os" + "strconv" "strings" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{ Name: "pod-id-file", Usage: "Write the pod ID to the file", }, + cli.StringSliceFlag{ + Name: "publish, p", + Usage: "Publish a container's port, or a range of ports, to the host (default [])", + }, cli.StringFlag{ Name: "share", Usage: "A comma delimited list of kernel namespaces the pod will share", @@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error { defer podIdFile.Close() defer podIdFile.Sync() } + + if len(c.StringSlice("publish")) > 0 { + if !c.BoolT("infra") { + return errors.Errorf("you must have an infra container to publish port bindings to the host") + } + if rootless.IsRootless() { + return errors.Errorf("rootless networking does not allow port binding to the host") + } + } + if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } @@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error { options = append(options, nsOptions...) } + if len(c.StringSlice("publish")) > 0 { + portBindings, err := CreatePortBindings(c.StringSlice("publish")) + if err != nil { + return err + } + options = append(options, libpod.WithInfraContainerPorts(portBindings)) + + } // always have containers use pod cgroups // User Opt out is not yet supported options = append(options, libpod.WithPodCgroups()) @@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error { return nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + // The conversion from []string to natBindings is temporary while mheon reworks the port + // deduplication code. Eventually that step will not be required. + _, natBindings, err := nat.ParsePortSpecs(ports) + if err != nil { + return nil, err + } + for containerPb, hostPb := range natBindings { + var pm ocicni.PortMapping + pm.ContainerPort = int32(containerPb.Int()) + for _, i := range hostPb { + var hostPort int + var err error + pm.HostIP = i.HostIP + if i.HostPort == "" { + hostPort = containerPb.Int() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} diff --git a/completions/bash/podman b/completions/bash/podman index c029f893a..222511a3c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2178,12 +2178,14 @@ _podman_pod_create() { --cgroup-parent --infra-command --infra-image - --share - --podidfile --label-file --label -l --name + --podidfile + --publish + -p + --share " local boolean_options=" diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md index 673ad9a8c..a63b12d73 100644 --- a/docs/podman-pod-create.1.md +++ b/docs/podman-pod-create.1.md @@ -51,6 +51,15 @@ Assign a name to the pod Write the pod ID to the file +**-p**, **--publish**=[] + +Publish a port or range of ports from the pod to the host + +Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` +Both hostPort and containerPort can be specified as a range of ports. +When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` + **--share**="" A comma deliminated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. diff --git a/libpod/options.go b/libpod/options.go index 8d044313b..507847d65 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1295,3 +1295,14 @@ func WithInfraContainer() PodCreateOption { return nil } } + +// WithInfraContainerPorts tells the pod to add port bindings to the pause container +func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return ErrPodFinalized + } + pod.config.InfraContainer.PortBindings = bindings + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 8ac976f6a..07f41f5c6 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -4,6 +4,7 @@ import ( "time" "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" ) @@ -96,7 +97,8 @@ type PodContainerInfo struct { // InfraContainerConfig is the configuration for the pod's infra container type InfraContainerConfig struct { - HasInfraContainer bool `json:"makeInfraContainer"` + HasInfraContainer bool `json:"makeInfraContainer"` + PortBindings []ocicni.PortMapping `json:"infraPortBindings"` } // ID retrieves the pod's ID diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go index 6c1c939f3..8ea9a5e72 100644 --- a/libpod/pod_easyjson.go +++ b/libpod/pod_easyjson.go @@ -6,6 +6,7 @@ package libpod import ( json "encoding/json" + ocicni "github.com/cri-o/ocicni/pkg/ocicni" easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" @@ -721,6 +722,29 @@ func easyjsonBe091417DecodeGithubComContainersLibpodLibpod5(in *jlexer.Lexer, ou switch key { case "makeInfraContainer": out.HasInfraContainer = bool(in.Bool()) + case "infraPortBindings": + if in.IsNull() { + in.Skip() + out.PortBindings = nil + } else { + in.Delim('[') + if out.PortBindings == nil { + if !in.IsDelim(']') { + out.PortBindings = make([]ocicni.PortMapping, 0, 1) + } else { + out.PortBindings = []ocicni.PortMapping{} + } + } else { + out.PortBindings = (out.PortBindings)[:0] + } + for !in.IsDelim(']') { + var v6 ocicni.PortMapping + easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in, &v6) + out.PortBindings = append(out.PortBindings, v6) + in.WantComma() + } + in.Delim(']') + } default: in.SkipRecursive() } @@ -745,5 +769,109 @@ func easyjsonBe091417EncodeGithubComContainersLibpodLibpod5(out *jwriter.Writer, } out.Bool(bool(in.HasInfraContainer)) } + { + const prefix string = ",\"infraPortBindings\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + if in.PortBindings == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v7, v8 := range in.PortBindings { + if v7 > 0 { + out.RawByte(',') + } + easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out, v8) + } + out.RawByte(']') + } + } + out.RawByte('}') +} +func easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "hostPort": + out.HostPort = int32(in.Int32()) + case "containerPort": + out.ContainerPort = int32(in.Int32()) + case "protocol": + out.Protocol = string(in.String()) + case "hostIP": + out.HostIP = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out *jwriter.Writer, in ocicni.PortMapping) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"hostPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.HostPort)) + } + { + const prefix string = ",\"containerPort\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int32(int32(in.ContainerPort)) + } + { + const prefix string = ",\"protocol\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Protocol)) + } + { + const prefix string = ",\"hostIP\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.HostIP)) + } out.RawByte('}') } diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index fea79e994..450a2fb32 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" ) @@ -50,9 +49,8 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID options = append(options, withIsInfra()) // Since user namespace sharing is not implemented, we only need to check if it's rootless - portMappings := make([]ocicni.PortMapping, 0) networks := make([]string, 0) - options = append(options, WithNetNS(portMappings, isRootless, networks)) + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks)) return r.newContainer(ctx, g.Config, options...) } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6ac9d82da..6a0642ee7 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib } options = append(options, runtime.WithPod(pod)) } - if len(c.PortBindings) > 0 { portBindings, err = c.CreatePortBindings() if err != nil { diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 51522ffd1..5abf9613b 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -80,4 +80,43 @@ var _ = Describe("Podman pod create", func() { check.WaitWithDefaultTimeout() Expect(len(check.OutputToStringArray())).To(Equal(0)) }) + + It("podman create pod without network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(1)) + }) + + It("podman create pod with network portbindings", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + pod := session.OutputToString() + + webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx}) + webserver.WaitWithDefaultTimeout() + Expect(webserver.ExitCode()).To(Equal(0)) + + check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check.WaitWithDefaultTimeout() + Expect(check.ExitCode()).To(Equal(0)) + }) + + It("podman create pod with no infra but portbindings should fail", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--infra=false", "--name", name, "-p", "80:80"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) }) -- cgit v1.2.3-54-g00ecf From ff47a4c2d5485fc49f937f3ce0c4e2fd6bdb1956 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Tue, 20 Nov 2018 14:08:08 +0000 Subject: Use a struct to pass options to Checkpoint() For upcoming changes to the Checkpoint() functions this commit switches checkpoint options from a boolean to a struct, so that additional options can be passed easily to Checkpoint() without changing the function parameters all the time. Signed-off-by: Adrian Reber --- cmd/podman/checkpoint.go | 6 ++++-- libpod/container_api.go | 10 ++++++++-- libpod/container_internal_linux.go | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index bf280920d..49e2aec63 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -50,7 +50,9 @@ func checkpointCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + } if err := checkAllAndLatest(c); err != nil { return err @@ -59,7 +61,7 @@ func checkpointCmd(c *cli.Context) error { containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { - if err = ctr.Checkpoint(context.TODO(), keep); err != nil { + if err = ctr.Checkpoint(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/libpod/container_api.go b/libpod/container_api.go index 390987394..9f5436b14 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -830,8 +830,14 @@ func (c *Container) Refresh(ctx context.Context) error { return nil } +// ContainerCheckpointOptions is a struct used to pass the parameters +// for checkpointing to corresponding functions +type ContainerCheckpointOptions struct { + Keep bool +} + // Checkpoint checkpoints a container -func (c *Container) Checkpoint(ctx context.Context, keep bool) error { +func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { logrus.Debugf("Trying to checkpoint container %s", c) if !c.batched { c.lock.Lock() @@ -842,7 +848,7 @@ func (c *Container) Checkpoint(ctx context.Context, keep bool) error { } } - return c.checkpoint(ctx, keep) + return c.checkpoint(ctx, options) } // Restore restores a container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 66c7e8a04..003e8284a 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -431,7 +431,7 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr return nil } -func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { if !criu.CheckForCriu() { return errors.Errorf("checkpointing a container requires at least CRIU %d", criu.MinCriuVersion) @@ -464,7 +464,7 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { return err } - if !keep { + if !options.Keep { // Remove log file os.Remove(filepath.Join(c.bundlePath(), "dump.log")) // Remove statistic file -- cgit v1.2.3-54-g00ecf From b0572d622974837c2221ed1f01a2ab982f078370 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Tue, 20 Nov 2018 15:34:15 +0000 Subject: Added option to keep containers running after checkpointing CRIU supports to leave processes running after checkpointing: -R|--leave-running leave tasks in running state after checkpoint runc also support to leave containers running after checkpointing: --leave-running leave the process running after checkpointing With this commit the support to leave a container running after checkpointing is brought to Podman: --leave-running, -R leave the container running after writing checkpoint to disk Now it is possible to checkpoint a container at some point in time without stopping the container. This can be used to rollback the container to an early state: $ podman run --tmpfs /tmp --name podman-criu-test -d docker://docker.io/yovfiatbeb/podman-criu-test $ curl 10.88.64.253:8080/examples/servlets/servlet/HelloWorldExample 3 $ podman container checkpoint -R -l $ curl 10.88.64.253:8080/examples/servlets/servlet/HelloWorldExample 4 $ curl 10.88.64.253:8080/examples/servlets/servlet/HelloWorldExample 5 $ podman stop -l $ podman container restore -l $ curl 10.88.64.253:8080/examples/servlets/servlet/HelloWorldExample 4 So after checkpointing the container kept running and was stopped after some time. Restoring this container will restore the state right at the checkpoint. Signed-off-by: Adrian Reber --- cmd/podman/checkpoint.go | 7 ++++++- libpod/container_api.go | 3 ++- libpod/container_internal_linux.go | 12 +++++++----- libpod/oci.go | 15 ++++++++++++--- 4 files changed, 27 insertions(+), 10 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index 49e2aec63..ddfd12bc3 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -23,6 +23,10 @@ var ( Name: "keep, k", Usage: "keep all temporary checkpoint files", }, + cli.BoolFlag{ + Name: "leave-running, R", + Usage: "leave the container running after writing checkpoint to disk", + }, cli.BoolFlag{ Name: "all, a", Usage: "checkpoint all running containers", @@ -51,7 +55,8 @@ func checkpointCmd(c *cli.Context) error { defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), + Keep: c.Bool("keep"), + KeepRunning: c.Bool("leave-running"), } if err := checkAllAndLatest(c); err != nil { diff --git a/libpod/container_api.go b/libpod/container_api.go index 9f5436b14..df6b6e962 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -833,7 +833,8 @@ func (c *Container) Refresh(ctx context.Context) error { // ContainerCheckpointOptions is a struct used to pass the parameters // for checkpointing to corresponding functions type ContainerCheckpointOptions struct { - Keep bool + Keep bool + KeepRunning bool } // Checkpoint checkpoints a container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 003e8284a..e6071945d 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -440,7 +440,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) } - if err := c.runtime.ociRuntime.checkpointContainer(c); err != nil { + if err := c.runtime.ociRuntime.checkpointContainer(c, options); err != nil { return err } @@ -457,11 +457,13 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO logrus.Debugf("Checkpointed container %s", c.ID()) - c.state.State = ContainerStateStopped + if !options.KeepRunning { + c.state.State = ContainerStateStopped - // Cleanup Storage and Network - if err := c.cleanup(ctx); err != nil { - return err + // Cleanup Storage and Network + if err := c.cleanup(ctx); err != nil { + return err + } } if !options.Keep { diff --git a/libpod/oci.go b/libpod/oci.go index 71da830b5..8ee2c948f 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -844,13 +844,22 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error { } // checkpointContainer checkpoints the given container -func (r *OCIRuntime) checkpointContainer(ctr *Container) error { +func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error { // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() // workPath will be used to store dump.log and stats-dump workPath := ctr.bundlePath() logrus.Debugf("Writing checkpoint to %s", imagePath) logrus.Debugf("Writing checkpoint logs to %s", workPath) - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "checkpoint", - "--image-path", imagePath, "--work-path", workPath, ctr.ID()) + args := []string{} + args = append(args, "checkpoint") + args = append(args, "--image-path") + args = append(args, imagePath) + args = append(args, "--work-path") + args = append(args, workPath) + if options.KeepRunning { + args = append(args, "--leave-running") + } + args = append(args, ctr.ID()) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } -- cgit v1.2.3-54-g00ecf From 43c20d02006fb83d05414a2f868b1c0a4dd4346b Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Fri, 23 Nov 2018 22:25:45 +0100 Subject: implement --format for version command Signed-off-by: Tomas Tomecek --- cmd/podman/version.go | 33 ++++++++++++++++++++++++++++----- completions/bash/podman | 15 ++++++++++----- docs/podman-version.1.md | 23 +++++++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/version.go b/cmd/podman/version.go index d80f24a14..d81deb696 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -15,6 +16,19 @@ func versionCmd(c *cli.Context) error { if err != nil { errors.Wrapf(err, "unable to determine version") } + + versionOutputFormat := c.String("format") + if versionOutputFormat != "" { + var out formats.Writer + switch versionOutputFormat { + case formats.JSONString: + out = formats.JSONStruct{Output: output} + default: + out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} + } + formats.Writer(out).Out() + return nil + } fmt.Println("Version: ", output.Version) fmt.Println("Go Version: ", output.GoVersion) if output.GitCommit != "" { @@ -30,8 +44,17 @@ func versionCmd(c *cli.Context) error { } // Cli command to print out the full version of podman -var versionCommand = cli.Command{ - Name: "version", - Usage: "Display the PODMAN Version Information", - Action: versionCmd, -} +var ( + versionCommand = cli.Command{ + Name: "version", + Usage: "Display the Podman Version Information", + Action: versionCmd, + Flags: versionFlags, + } + versionFlags = []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Change the output format to JSON or a Go template", + }, + } +) diff --git a/completions/bash/podman b/completions/bash/podman index 222511a3c..8eaa1e6a9 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1906,11 +1906,16 @@ _podman_top() { } _podman_version() { - local options_with_args=" - " - local boolean_options=" - " - _complete_ "$options_with_args" "$boolean_options" + local boolean_options=" + --help + -h + " + local options_with_args=" + --format + " + local all_options="$options_with_args $boolean_options" + + _complete_ "$options_with_args" "$boolean_options" } _podman_save() { diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md index 0c9b9ceed..749a33afd 100644 --- a/docs/podman-version.1.md +++ b/docs/podman-version.1.md @@ -16,8 +16,31 @@ OS, and Architecture. Print usage statement +**--format** + +Change output format to "json" or a Go template. + +## Example + +A sample output of the `version` command: +``` +$ podman version +Version: 0.11.1 +Go Version: go1.11 +Git Commit: "8967a1d691ed44896b81ad48c863033f23c65eb0-dirty" +Built: Thu Nov 8 22:35:40 2018 +OS/Arch: linux/amd64 +``` + +Filtering out only the version: +``` +$ podman version --format '{{.Version}}' +0.11.2 +``` + ## SEE ALSO podman(1), crio(8) ## HISTORY +November 2018, Added --format flag by Tomas Tomecek July 2017, Originally compiled by Urvashi Mohnani -- cgit v1.2.3-54-g00ecf From 9d883d2032b112d5c65040629313cfba0de6c479 Mon Sep 17 00:00:00 2001 From: baude Date: Sun, 25 Nov 2018 16:26:39 -0600 Subject: add podman container|image exists Add an exists subcommand to podman container and podman image that allows users to verify the existence of a container or image by ID or name. The return code can be 0 (success), 1 (failed to find), or 125 (failed to work with runtime). Issue #1845 Signed-off-by: baude --- cmd/podman/container.go | 1 + cmd/podman/exists.go | 83 ++++++++++++++++++++++++++++++++++++++ cmd/podman/image.go | 1 + completions/bash/podman | 16 ++++++++ docs/podman-container-exists.1.md | 40 ++++++++++++++++++ docs/podman-container.1.md | 1 + docs/podman-image-exists.1.md | 40 ++++++++++++++++++ docs/podman-image.1.md | 1 + libpod/image/errors.go | 15 +++++++ libpod/image/image.go | 4 +- test/e2e/exists_test.go | 85 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 cmd/podman/exists.go create mode 100644 docs/podman-container-exists.1.md create mode 100644 docs/podman-image-exists.1.md create mode 100644 libpod/image/errors.go create mode 100644 test/e2e/exists_test.go (limited to 'cmd') diff --git a/cmd/podman/container.go b/cmd/podman/container.go index ff634278f..b6262f890 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -9,6 +9,7 @@ var ( attachCommand, checkpointCommand, cleanupCommand, + containerExistsCommand, commitCommand, createCommand, diffCommand, diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go new file mode 100644 index 000000000..2f7b7c185 --- /dev/null +++ b/cmd/podman/exists.go @@ -0,0 +1,83 @@ +package main + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + imageExistsDescription = ` + podman image exists + + Check if an image exists in local storage +` + + imageExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if an image exists in local storage", + Description: imageExistsDescription, + Action: imageExistsCmd, + ArgsUsage: "IMAGE-NAME", + OnUsageError: usageErrorHandler, + } +) + +var ( + containerExistsDescription = ` + podman container exists + + Check if a container exists in local storage +` + + containerExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a container exists in local storage", + Description: containerExistsDescription, + Action: containerExistsCmd, + ArgsUsage: "CONTAINER-NAME", + OnUsageError: usageErrorHandler, + } +) + +func imageExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one image at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.ImageRuntime().NewFromLocal(args[0]); err != nil { + if errors.Cause(err) == image.ErrNoSuchImage { + os.Exit(1) + } + return err + } + return nil +} + +func containerExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one container at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.LookupContainer(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + os.Exit(1) + } + return err + } + return nil +} diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e67f61799..418b442e3 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -9,6 +9,7 @@ var ( buildCommand, historyCommand, importCommand, + imageExistsCommand, inspectCommand, loadCommand, lsImagesCommand, diff --git a/completions/bash/podman b/completions/bash/podman index 8eaa1e6a9..3c6b6ec50 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2178,6 +2178,22 @@ _podman_container_runlabel() { esac } +_podman_container_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + +_podman_image_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + _podman_pod_create() { local options_with_args=" --cgroup-parent diff --git a/docs/podman-container-exists.1.md b/docs/podman-container-exists.1.md new file mode 100644 index 000000000..76701e2c2 --- /dev/null +++ b/docs/podman-container-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-container-exists- Check if a container exists in local storage + +# SYNOPSIS +**podman container exists** +[**-h**|**--help**] +CONTAINER + +# DESCRIPTION +**podman container exists** checks if a container exists in local storage. The **ID** or **Name** +of the container may be used as input. Podman will return an exit code +of `0` when the container is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an container called `webclient` exists in local storage (the container does actually exist). +``` +$ sudo podman container exists webclient +$ echo $? +0 +$ +``` + +Check if an container called `webbackend` exists in local storage (the container does not actually exist). +``` +$ sudo podman container exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md index 67d42bfef..aa5dfa82c 100644 --- a/docs/podman-container.1.md +++ b/docs/podman-container.1.md @@ -20,6 +20,7 @@ The container command allows you to manage containers | create | [podman-create(1)](podman-create.1.md) | Create a new container. | | diff | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | | exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | +| exists | [podman-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage | | export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | diff --git a/docs/podman-image-exists.1.md b/docs/podman-image-exists.1.md new file mode 100644 index 000000000..e04c23721 --- /dev/null +++ b/docs/podman-image-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-image-exists- Check if an image exists in local storage + +# SYNOPSIS +**podman image exists** +[**-h**|**--help**] +IMAGE + +# DESCRIPTION +**podman image exists** checks if an image exists in local storage. The **ID** or **Name** +of the image may be used as input. Podman will return an exit code +of `0` when the image is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an image called `webclient` exists in local storage (the image does actually exist). +``` +$ sudo podman image exists webclient +$ echo $? +0 +$ +``` + +Check if an image called `webbackend` exists in local storage (the image does not actually exist). +``` +$ sudo podman image exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 33de0456f..446f8667d 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -14,6 +14,7 @@ The image command allows you to manage images | Command | Man Page | Description | | -------- | ----------------------------------------- | ------------------------------------------------------------------------------ | | build | [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. | +| exists | [podman-exists(1)](podman-image-exists.1.md) | Check if a image exists in local storage | | history | [podman-history(1)](podman-history.1.md) | Show the history of an image. | | import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. | diff --git a/libpod/image/errors.go b/libpod/image/errors.go new file mode 100644 index 000000000..4088946cb --- /dev/null +++ b/libpod/image/errors.go @@ -0,0 +1,15 @@ +package image + +import ( + "errors" +) + +// Copied directly from libpod errors to avoid circular imports +var ( + // ErrNoSuchCtr indicates the requested container does not exist + ErrNoSuchCtr = errors.New("no such container") + // ErrNoSuchPod indicates the requested pod does not exist + ErrNoSuchPod = errors.New("no such pod") + // ErrNoSuchImage indicates the requested image does not exist + ErrNoSuchImage = errors.New("no such image") +) diff --git a/libpod/image/image.go b/libpod/image/image.go index 7e520d97e..a05c15160 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -252,7 +252,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { // The image has a registry name in it and we made sure we looked for it locally // with a tag. It cannot be local. if decomposedImage.hasRegistry { - return nil, errors.Errorf("%s", imageError) + return nil, errors.Wrapf(ErrNoSuchImage, imageError) } @@ -275,7 +275,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { return repoImage, nil } - return nil, errors.Wrapf(err, imageError) + return nil, errors.Wrapf(ErrNoSuchImage, err.Error()) } // ID returns the image ID as a string diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go new file mode 100644 index 000000000..9165e8902 --- /dev/null +++ b/test/e2e/exists_test.go @@ -0,0 +1,85 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image|container exists", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + + }) + It("podman image exists in local storage by fq name", func() { + session := podmanTest.Podman([]string{"image", "exists", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image exists in local storage by short name", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image does not exist in local storage", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine9999"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + It("podman container exists in local storage by name", func() { + setup := podmanTest.RunTopContainer("foobar") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by short container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString()[0:12] + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container does not exist in local storage", func() { + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + +}) -- cgit v1.2.3-54-g00ecf From 4203df69aca13f14e43ad32a9b7ffb6cfb8c1016 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 21 Nov 2018 17:38:28 +0100 Subject: rootless: add new netmode "slirp4netns" so that inspect reports the correct network configuration. Closes: https://github.com/containers/libpod/issues/1453 Signed-off-by: Giuseppe Scrivano --- cmd/podman/common.go | 10 +++++++++- pkg/namespaces/namespaces.go | 7 ++++++- pkg/spec/spec.go | 3 +++ pkg/varlinkapi/containers_create.go | 7 ++++++- test/e2e/rootless_test.go | 8 ++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f9e746b28..c4016698a 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" @@ -161,6 +162,13 @@ func getContext() context.Context { return context.TODO() } +func getDefaultNetwork() string { + if rootless.IsRootless() { + return "slirp4netns" + } + return "bridge" +} + // Common flags shared between commands var createFlags = []cli.Flag{ cli.StringSliceFlag{ @@ -372,7 +380,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{ Name: "net, network", Usage: "Connect a container to a network", - Value: "bridge", + Value: getDefaultNetwork(), }, cli.BoolFlag{ Name: "oom-kill-disable", diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index bee833fa9..832efd554 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -223,7 +223,12 @@ func (n NetworkMode) IsBridge() bool { return n == "bridge" } +// IsSlirp4netns indicates if we are running a rootless network stack +func (n NetworkMode) IsSlirp4netns() bool { + return n == "slirp4netns" +} + // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { - return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() + return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns() } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b1cca2c9e..05be00864 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -453,6 +453,9 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error { } else if IsPod(string(netMode)) { logrus.Debug("Using pod netmode, unless pod is not sharing") return nil + } else if netMode.IsSlirp4netns() { + logrus.Debug("Using slirp4netns netmode") + return nil } else if netMode.IsUserDefined() { logrus.Debug("Using user defined netmode") return nil diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index ca1a57048..f9a2db9c8 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/docker/docker/pkg/signal" @@ -126,7 +127,11 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru // NETWORK MODE networkMode := create.Net_mode if networkMode == "" { - networkMode = "bridge" + if rootless.IsRootless() { + networkMode = "slirp4netns" + } else { + networkMode = "bridge" + } } // WORKING DIR diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index 995744ae5..9f84d4c13 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -217,6 +217,14 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) + if len(args) == 0 { + cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l"}, 1000, 1000, env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) + data := cmd.InspectContainerToJSON() + Expect(data[0].HostConfig.NetworkMode).To(ContainSubstring("slirp4netns")) + } + if !canUseExec { Skip("ioctl(NS_GET_PARENT) not supported.") } -- cgit v1.2.3-54-g00ecf From 0592558289c354447d28903910f4165ac5acd71a Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Wed, 21 Nov 2018 13:09:17 +0000 Subject: Use also a struct to pass options to Restore() This is basically the same change as ff47a4c2d5485fc49f937f3ce0c4e2fd6bdb1956 (Use a struct to pass options to Checkpoint()) just for the Restore() function. It is used to pass multiple restore options to the API and down to conmon which is used to restore containers. This is for the upcoming changes to support checkpointing and restoring containers with '--tcp-established'. Signed-off-by: Adrian Reber --- cmd/podman/restore.go | 6 ++++-- libpod/container_api.go | 6 +++--- libpod/container_internal.go | 2 +- libpod/container_internal_linux.go | 6 +++--- libpod/oci.go | 4 ++-- libpod/oci_linux.go | 6 +++--- libpod/oci_unsupported.go | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 067a2b5d4..6383ebf0b 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -53,7 +53,9 @@ func restoreCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + } if err := checkAllAndLatest(c); err != nil { return err @@ -62,7 +64,7 @@ func restoreCmd(c *cli.Context) error { containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "checkpointed") for _, ctr := range containers { - if err = ctr.Restore(context.TODO(), keep); err != nil { + if err = ctr.Restore(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/libpod/container_api.go b/libpod/container_api.go index df6b6e962..396f06c20 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -831,7 +831,7 @@ func (c *Container) Refresh(ctx context.Context) error { } // ContainerCheckpointOptions is a struct used to pass the parameters -// for checkpointing to corresponding functions +// for checkpointing (and restoring) to the corresponding functions type ContainerCheckpointOptions struct { Keep bool KeepRunning bool @@ -853,7 +853,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO } // Restore restores a container -func (c *Container) Restore(ctx context.Context, keep bool) (err error) { +func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { logrus.Debugf("Trying to restore container %s", c) if !c.batched { c.lock.Lock() @@ -864,5 +864,5 @@ func (c *Container) Restore(ctx context.Context, keep bool) (err error) { } } - return c.restore(ctx, keep) + return c.restore(ctx, options) } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 051e0aeb7..da0456554 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -606,7 +606,7 @@ func (c *Container) init(ctx context.Context) error { } // With the spec complete, do an OCI create - if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, false); err != nil { + if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, nil); err != nil { return err } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index ffb82cc94..99f8652df 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -514,7 +514,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO return c.save() } -func (c *Container) restore(ctx context.Context, keep bool) (err error) { +func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { if !criu.CheckForCriu() { return errors.Errorf("restoring a container requires at least CRIU %d", criu.MinCriuVersion) @@ -602,7 +602,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { // Cleanup for a working restore. c.removeConmonFiles() - if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, true); err != nil { + if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, &options); err != nil { return err } @@ -610,7 +610,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) { c.state.State = ContainerStateRunning - if !keep { + if !options.Keep { // Delete all checkpoint related files. At this point, in theory, all files // should exist. Still ignoring errors for now as the container should be // restored and running. Not erroring out just because some cleanup operation diff --git a/libpod/oci.go b/libpod/oci.go index a7aec06e5..6aedc5662 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -227,7 +227,7 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { return files, nil } -func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { var stderrBuf bytes.Buffer runtimeDir, err := util.GetRootlessRuntimeDir() @@ -289,7 +289,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res args = append(args, "--syslog") } - if restoreContainer { + if restoreOptions != nil { args = append(args, "--restore", ctr.CheckpointPath()) } diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index b159eae78..2737a641e 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -65,10 +65,10 @@ func newPipe() (parent *os.File, child *os.File, err error) { // CreateContainer creates a container in the OCI runtime // TODO terminal support for container // Presently just ignoring conmon opts related to it -func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { if ctr.state.UserNSRoot == "" { // no need of an intermediate mount ns - return r.createOCIContainer(ctr, cgroupParent, restoreContainer) + return r.createOCIContainer(ctr, cgroupParent, restoreOptions) } var wg sync.WaitGroup wg.Add(1) @@ -106,7 +106,7 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor if err != nil { return } - err = r.createOCIContainer(ctr, cgroupParent, restoreContainer) + err = r.createOCIContainer(ctr, cgroupParent, restoreOptions) }() wg.Wait() diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go index b133eb402..8c084d1e2 100644 --- a/libpod/oci_unsupported.go +++ b/libpod/oci_unsupported.go @@ -15,7 +15,7 @@ func newPipe() (parent *os.File, child *os.File, err error) { return nil, nil, ErrNotImplemented } -func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) { +func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) { return ErrNotImplemented } -- cgit v1.2.3-54-g00ecf From 03c88a3debf77780bdad2382d4c01ccedc6d27a5 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Wed, 21 Nov 2018 14:40:43 +0000 Subject: Added tcp-established to checkpoint/restore CRIU can checkpoint and restore processes/containers with established TCP connections if the correct option is specified. To implement checkpoint and restore with support for established TCP connections with Podman this commit adds the necessary options to runc during checkpoint and also tells conmon during restore to use 'runc restore' with '--tcp-established'. For this Podman feature to work a corresponding conmon change is required. Example: $ podman run --tmpfs /tmp --name podman-criu-test -d docker://docker.io/yovfiatbeb/podman-criu-test $ nc `podman inspect -l | jq -r '.[0].NetworkSettings.IPAddress'` 8080 GET /examples/servlets/servlet/HelloWorldExample Connection: keep-alive 1 GET /examples/servlets/servlet/HelloWorldExample Connection: keep-alive 2 $ # Using HTTP keep-alive multiple requests are send to the server in the container $ # Different terminal: $ podman container checkpoint -l criu failed: type NOTIFY errno 0 $ # Looking at the log file would show errors because of established TCP connections $ podman container checkpoint -l --tcp-established $ # This works now and after the restore the same connection as above can be used for requests $ podman container restore -l --tcp-established The restore would fail without '--tcp-established' as the checkpoint image contains established TCP connections. Signed-off-by: Adrian Reber --- cmd/podman/checkpoint.go | 9 +++++++-- cmd/podman/restore.go | 7 ++++++- libpod/container_api.go | 8 +++++++- libpod/oci.go | 6 ++++++ 4 files changed, 26 insertions(+), 4 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index ddfd12bc3..824c97662 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -27,6 +27,10 @@ var ( Name: "leave-running, R", Usage: "leave the container running after writing checkpoint to disk", }, + cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, cli.BoolFlag{ Name: "all, a", Usage: "checkpoint all running containers", @@ -55,8 +59,9 @@ func checkpointCmd(c *cli.Context) error { defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), - KeepRunning: c.Bool("leave-running"), + Keep: c.Bool("keep"), + KeepRunning: c.Bool("leave-running"), + TCPEstablished: c.Bool("tcp-established"), } if err := checkAllAndLatest(c); err != nil { diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 6383ebf0b..afdbc36e0 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -26,6 +26,10 @@ var ( // restore --all would make more sense if there would be // dedicated state for container which are checkpointed. // TODO: add ContainerStateCheckpointed + cli.BoolFlag{ + Name: "tcp-established", + Usage: "checkpoint a container with established TCP connections", + }, cli.BoolFlag{ Name: "all, a", Usage: "restore all checkpointed containers", @@ -54,7 +58,8 @@ func restoreCmd(c *cli.Context) error { defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), + Keep: c.Bool("keep"), + TCPEstablished: c.Bool("tcp-established"), } if err := checkAllAndLatest(c); err != nil { diff --git a/libpod/container_api.go b/libpod/container_api.go index 396f06c20..aee87cc04 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -833,8 +833,14 @@ func (c *Container) Refresh(ctx context.Context) error { // ContainerCheckpointOptions is a struct used to pass the parameters // for checkpointing (and restoring) to the corresponding functions type ContainerCheckpointOptions struct { - Keep bool + // Keep tells the API to not delete checkpoint artifacts + Keep bool + // KeepRunning tells the API to keep the container running + // after writing the checkpoint to disk KeepRunning bool + // TCPEstablished tells the API to checkpoint a container + // even if it contains established TCP connections + TCPEstablished bool } // Checkpoint checkpoints a container diff --git a/libpod/oci.go b/libpod/oci.go index 6aedc5662..5a041b7d6 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -291,6 +291,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res if restoreOptions != nil { args = append(args, "--restore", ctr.CheckpointPath()) + if restoreOptions.TCPEstablished { + args = append(args, "--restore-arg", "--tcp-established") + } } logrus.WithFields(logrus.Fields{ @@ -862,6 +865,9 @@ func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckp if options.KeepRunning { args = append(args, "--leave-running") } + if options.TCPEstablished { + args = append(args, "--tcp-established") + } args = append(args, ctr.ID()) return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } -- cgit v1.2.3-54-g00ecf From fbe8e23ce6504ad2dd669af01015840544f8f99f Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Mon, 26 Nov 2018 15:44:28 +0000 Subject: Fix podman container restore -a podman container restore -a was using the wrong filter to restore checkpointed containers. This switches from 'running' containers to 'exited' containers. Restoring with -a only works if all exited containers have been checkpointed. Maybe it would make sense to track which containers have been really checkpointed. This is just to fix '-a' to work at least if all exited containers have been checkpointed. Signed-off-by: Adrian Reber --- cmd/podman/restore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index afdbc36e0..bc2a71ba0 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -66,7 +66,7 @@ func restoreCmd(c *cli.Context) error { return err } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "checkpointed") + containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed") for _, ctr := range containers { if err = ctr.Restore(context.TODO(), options); err != nil { -- cgit v1.2.3-54-g00ecf From fc774ac0e5f6b906484723c600f43a66516a208a Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 27 Nov 2018 10:23:10 -0500 Subject: Stopping a stopped container is not an error for Podman Signed-off-by: Matthew Heon --- cmd/podman/stop.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 04022839a..ade51705e 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -77,7 +78,11 @@ func stopCmd(c *cli.Context) error { stopTimeout = ctr.StopTimeout() } f := func() error { - return con.StopWithTimeout(stopTimeout) + if err := con.StopWithTimeout(stopTimeout); err != nil && errors.Cause(err) != libpod.ErrCtrStopped { + return err + } + return nil + } stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ ContainerID: con.ID(), -- cgit v1.2.3-54-g00ecf From 61d4db480650c57d383990750ed87990e903f4da Mon Sep 17 00:00:00 2001 From: baude Date: Tue, 27 Nov 2018 10:33:19 -0600 Subject: Fix golang formatting issues Whe running unittests on newer golang versions, we observe failures with some formatting types when no declared correctly. Signed-off-by: baude --- cmd/podman/commit.go | 2 +- cmd/podman/images.go | 4 ++-- cmd/podman/import.go | 2 +- cmd/podman/inspect.go | 6 +++--- libpod/container.go | 4 ++-- libpod/container_api.go | 14 +++++++------- libpod/container_internal.go | 2 +- libpod/container_internal_linux.go | 2 +- libpod/kube.go | 2 +- libpod/pod_internal.go | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index b09c6b0d9..02ede4f73 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -95,7 +95,7 @@ func commitCmd(c *cli.Context) error { for _, change := range c.StringSlice("change") { splitChange := strings.Split(strings.ToUpper(change), "=") if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { - return errors.Errorf("invalid syntax for --change ", change) + return errors.Errorf("invalid syntax for --change: %s", change) } } } diff --git a/cmd/podman/images.go b/cmd/podman/images.go index c52b26260..3351123ed 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -376,13 +376,13 @@ func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, i case "before": before, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created())) case "after": after, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) if err != nil { - return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1]) + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created())) case "dangling": diff --git a/cmd/podman/import.go b/cmd/podman/import.go index be516e4fa..144354fa6 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -139,7 +139,7 @@ func downloadFromURL(source string) (string, error) { _, err = io.Copy(outFile, response.Body) if err != nil { - return "", errors.Wrapf(err, "error saving %q to %q", source, outFile) + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) } return outFile.Name(), nil diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index bd9e8c13c..6ffcde55f 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -119,7 +119,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) @@ -154,12 +154,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } else { libpodInspectData, err := ctr.Inspect(c.Bool("size")) if err != nil { - inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) if err != nil { - inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID) + inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID()) break } } diff --git a/libpod/container.go b/libpod/container.go index a8a58f4d8..b6155a809 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -829,7 +829,7 @@ func (c *Container) IPs() ([]net.IPNet, error) { } if !c.config.CreateNetNS { - return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") + return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID()) } ips := make([]net.IPNet, 0) @@ -857,7 +857,7 @@ func (c *Container) Routes() ([]types.Route, error) { } if !c.config.CreateNetNS { - return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod") + return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID()) } routes := make([]types.Route, 0) diff --git a/libpod/container_api.go b/libpod/container_api.go index 4789c0cd2..bc92cae69 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -39,7 +39,7 @@ func (c *Container) Init(ctx context.Context) (err error) { notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") @@ -93,7 +93,7 @@ func (c *Container) Start(ctx context.Context) (err error) { notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") @@ -159,7 +159,7 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, notRunning, err := c.checkDependenciesRunning() if err != nil { - return nil, errors.Wrapf(err, "error checking dependencies for container %s") + return nil, errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") @@ -718,7 +718,7 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e notRunning, err := c.checkDependenciesRunning() if err != nil { - return errors.Wrapf(err, "error checking dependencies for container %s") + return errors.Wrapf(err, "error checking dependencies for container %s", c.ID()) } if len(notRunning) > 0 { depString := strings.Join(notRunning, ",") @@ -803,7 +803,7 @@ func (c *Container) Refresh(ctx context.Context) error { return err } - logrus.Debugf("Successfully refresh container %s state") + logrus.Debugf("Successfully refresh container %s state", c.ID()) // Initialize the container if it was created in runc if wasCreated || wasRunning || wasPaused { @@ -847,7 +847,7 @@ type ContainerCheckpointOptions struct { // Checkpoint checkpoints a container func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { - logrus.Debugf("Trying to checkpoint container %s", c) + logrus.Debugf("Trying to checkpoint container %s", c.ID()) if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -862,7 +862,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO // Restore restores a container func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { - logrus.Debugf("Trying to restore container %s", c) + logrus.Debugf("Trying to restore container %s", c.ID()) if !c.batched { c.lock.Lock() defer c.lock.Unlock() diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 700773e7f..b616e0a07 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1191,7 +1191,7 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten if c.runtime.config.HooksDirNotExistFatal || !os.IsNotExist(err) { return nil, err } - logrus.Warnf("failed to load hooks: {}", err) + logrus.Warnf("failed to load hooks: %q", err) return nil, nil } hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 99f8652df..5fc94eb8a 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -764,7 +764,7 @@ func (c *Container) generateResolvConf() (string, error) { // Build resolv.conf if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { - return "", errors.Wrapf(err, "error building resolv.conf for container %s") + return "", errors.Wrapf(err, "error building resolv.conf for container %s", c.ID()) } // Relabel resolv.conf for the container diff --git a/libpod/kube.go b/libpod/kube.go index 00db0033b..1a5f80878 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -240,7 +240,7 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { if c.User() != "" { // It is *possible* that - logrus.Debug("Looking in container for user: %s", c.User()) + logrus.Debugf("Looking in container for user: %s", c.User()) u, err := lookup.GetUser(c.state.Mountpoint, c.User()) if err != nil { return nil, err diff --git a/libpod/pod_internal.go b/libpod/pod_internal.go index 46162c7ef..39a25c004 100644 --- a/libpod/pod_internal.go +++ b/libpod/pod_internal.go @@ -48,7 +48,7 @@ func (p *Pod) updatePod() error { // Save pod state to database func (p *Pod) save() error { if err := p.runtime.state.SavePod(p); err != nil { - return errors.Wrapf(err, "error saving pod %s state") + return errors.Wrapf(err, "error saving pod %s state", p.ID()) } return nil -- cgit v1.2.3-54-g00ecf From 8e30d685b7e2cf9e36b5d82278468958ab3e96b9 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 28 Nov 2018 12:30:07 -0600 Subject: hide kube command for now until the kube commands are ironed out, we dont want it drawing attention in any release Signed-off-by: baude --- cmd/podman/kube.go | 1 + 1 file changed, 1 insertion(+) (limited to 'cmd') diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go index ced87e2bd..2cb407c09 100644 --- a/cmd/podman/kube.go +++ b/cmd/podman/kube.go @@ -18,5 +18,6 @@ var ( Subcommands: kubeSubCommands, UseShortOptionHandling: true, OnUsageError: usageErrorHandler, + Hidden: true, } ) -- cgit v1.2.3-54-g00ecf From 82bcef38954b4d03b7e047d7843e77bc94647c82 Mon Sep 17 00:00:00 2001 From: baude Date: Fri, 30 Nov 2018 09:15:11 -0600 Subject: add pod short option to ps podman ps has a flag --pod; simply adding a short option of -p Signed-off-by: baude --- cmd/podman/ps.go | 2 +- completions/bash/podman | 2 +- docs/podman-ps.1.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 83274c9a8..0b03388a2 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -184,7 +184,7 @@ var ( Usage: "Display the extended information", }, cli.BoolFlag{ - Name: "pod", + Name: "pod, p", Usage: "Print the ID and name of the pod the containers are associated with", }, cli.BoolFlag{ diff --git a/completions/bash/podman b/completions/bash/podman index 076ec7ab4..3cccf2192 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2033,7 +2033,7 @@ _podman_ps() { --help -h --latest -l --no-trunc - --pod + --pod -p --quiet -q --size -s --namespace --ns diff --git a/docs/podman-ps.1.md b/docs/podman-ps.1.md index 2cb77ffed..7333a1095 100644 --- a/docs/podman-ps.1.md +++ b/docs/podman-ps.1.md @@ -24,7 +24,7 @@ all the containers information. By default it lists: Show all the containers, default is only running containers -**--pod** +**--pod, -p** Display the pods the containers are associated with -- cgit v1.2.3-54-g00ecf From 87df1ce01f19399f551e164542c5ed969eec1d20 Mon Sep 17 00:00:00 2001 From: baude Date: Sat, 1 Dec 2018 17:10:30 -0600 Subject: Add short-option handling to logs podman logs already supports the latest command line switch. users should be able to use the short-options combined (i.e. podman logs -lf). Signed-off-by: baude --- cmd/podman/logs.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 84aca5e61..75947c34e 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -40,14 +40,15 @@ var ( logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + "order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs" logsCommand = cli.Command{ - Name: "logs", - Usage: "Fetch the logs of a container", - Description: logsDescription, - Flags: sortFlags(logsFlags), - Action: logsCmd, - ArgsUsage: "CONTAINER", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + Name: "logs", + Usage: "Fetch the logs of a container", + Description: logsDescription, + Flags: sortFlags(logsFlags), + Action: logsCmd, + ArgsUsage: "CONTAINER", + SkipArgReorder: true, + OnUsageError: usageErrorHandler, + UseShortOptionHandling: true, } ) -- cgit v1.2.3-54-g00ecf From 562fa57dc9f497db772baa03bfa052082db68646 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Sun, 2 Dec 2018 15:21:35 -0500 Subject: Move rootless storage config into libpod Previous commits ensured that we would use database-configured paths if not explicitly overridden. However, our runtime generation did unconditionally override storage config, which made this useless. Move rootless storage configuration setup to libpod, and change storage setup so we only override if a setting is explicitly set, so we can still override what we want. Signed-off-by: Matthew Heon --- cmd/podman/create.go | 2 +- cmd/podman/libpodruntime/runtime.go | 34 +++++++++------------------- cmd/podman/run.go | 2 +- libpod/options.go | 24 ++++++++++++-------- libpod/runtime.go | 9 ++++++++ pkg/util/utils.go | 44 ++++++++++++++++++------------------- 6 files changed, 57 insertions(+), 58 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bcf830c7c..1ef9fa47a 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -66,7 +66,7 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index a4b3581be..13a821b9f 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -11,32 +11,18 @@ import ( // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { - storageOpts, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - 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 := util.GetDefaultStoreOptions() - if err != nil { - return nil, err - } - storageOpts.UIDMap = mappings.UIDMap - storageOpts.GIDMap = mappings.GIDMap - return GetRuntimeWithStorageOpts(c, &storageOpts) -} - -// GetRuntime generates a new libpod runtime configured by command line options -func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) { + storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} + if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") { + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + if err != nil { + return nil, err + } + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + } + if c.GlobalIsSet("root") { storageOpts.GraphRoot = c.GlobalString("root") } diff --git a/cmd/podman/run.go b/cmd/podman/run.go index af6ced45d..a4b5c918e 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -44,7 +44,7 @@ func runCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetContainerRuntime(c) + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/libpod/options.go b/libpod/options.go index 6783e2a39..661bd8d91 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -29,18 +29,18 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { return ErrRuntimeFinalized } - rt.config.StorageConfig.RunRoot = config.RunRoot if config.RunRoot != "" { + rt.config.StorageConfig.RunRoot = config.RunRoot rt.configuredFrom.storageRunRootSet = true } - rt.config.StorageConfig.GraphRoot = config.GraphRoot if config.GraphRoot != "" { + rt.config.StorageConfig.GraphRoot = config.GraphRoot rt.configuredFrom.storageGraphRootSet = true } - rt.config.StorageConfig.GraphDriverName = config.GraphDriverName if config.GraphDriverName != "" { + rt.config.StorageConfig.GraphDriverName = config.GraphDriverName rt.configuredFrom.storageGraphDriverSet = true } @@ -51,14 +51,20 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { rt.configuredFrom.libpodStaticDirSet = true } - rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) - copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + if config.GraphDriverOptions != nil { + rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) + copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions) + } - rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) - copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + if config.UIDMap != nil { + rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) + copy(rt.config.StorageConfig.UIDMap, config.UIDMap) + } - rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) - copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + if config.GIDMap != nil { + rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) + copy(rt.config.StorageConfig.GIDMap, config.GIDMap) + } return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index e01fa781b..6a5d2ad39 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -278,6 +278,15 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { deepcopier.Copy(defaultRuntimeConfig).To(runtime.config) runtime.config.TmpDir = tmpDir + if rootless.IsRootless() { + // If we're rootless, override the default storage config + storageConf, err := util.GetDefaultRootlessStoreOptions() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving rootless storage config") + } + runtime.config.StorageConfig = storageConf + } + configPath := ConfigPath foundConfig := true rootlessConfigPath := "" diff --git a/pkg/util/utils.go b/pkg/util/utils.go index de29bc5d8..78484eb78 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -313,33 +313,31 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { return config } -// GetDefaultStoreOptions returns the storage ops for containers -func GetDefaultStoreOptions() (storage.StoreOptions, error) { - storageOpts := storage.DefaultStoreOptions - if rootless.IsRootless() { - var err error - storageOpts, err = GetRootlessStorageOpts() +// GetDefaultStoreOptions returns the storage ops for containers. +func GetDefaultRootlessStoreOptions() (storage.StoreOptions, error) { + var err error + storageOpts, err := GetRootlessStorageOpts() + if err != nil { + return storageOpts, err + } + + storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + if _, err := os.Stat(storageConf); err == nil { + storage.ReloadConfigurationFile(storageConf, &storageOpts) + } else if os.IsNotExist(err) { + os.MkdirAll(filepath.Dir(storageConf), 0755) + file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) if err != nil { - return storageOpts, err + return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) } - storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") - if _, err := os.Stat(storageConf); err == nil { - storage.ReloadConfigurationFile(storageConf, &storageOpts) - } else if os.IsNotExist(err) { - os.MkdirAll(filepath.Dir(storageConf), 0755) - file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) - } - - tomlConfiguration := getTomlStorage(&storageOpts) - defer file.Close() - enc := toml.NewEncoder(file) - if err := enc.Encode(tomlConfiguration); err != nil { - os.Remove(storageConf) - } + tomlConfiguration := getTomlStorage(&storageOpts) + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tomlConfiguration); err != nil { + os.Remove(storageConf) } } + return storageOpts, nil } -- cgit v1.2.3-54-g00ecf From 5c02dda869390725a799339b094f548d327c9122 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 3 Dec 2018 09:15:29 -0600 Subject: Adding more varlink endpoints * runlabel * checkpoint * restore * container|image exists * mount * unmount Signed-off-by: baude --- API.md | 103 ++++++++++++++++++++++++++++++++++- cmd/podman/runlabel.go | 81 ++++----------------------- cmd/podman/shared/container.go | 81 +++++++++++++++++++++++++++ cmd/podman/varlink/io.podman.varlink | 52 ++++++++++++++++++ pkg/varlinkapi/containers.go | 49 +++++++++++++++++ pkg/varlinkapi/images.go | 45 +++++++++++++++ pkg/varlinkapi/mount.go | 49 +++++++++++++++++ 7 files changed, 389 insertions(+), 71 deletions(-) create mode 100644 pkg/varlinkapi/mount.go (limited to 'cmd') diff --git a/API.md b/API.md index 34d401aca..4d48e8245 100755 --- a/API.md +++ b/API.md @@ -9,6 +9,14 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) +[func ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) string](#ContainerCheckpoint) + +[func ContainerExists(name: string) int](#ContainerExists) + +[func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) + +[func ContainerRunlabel(runlabel: Runlabel) ](#ContainerRunlabel) + [func CreateContainer(create: Create) string](#CreateContainer) [func CreateImage() NotImplemented](#CreateImage) @@ -43,6 +51,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func HistoryImage(name: string) ImageHistory](#HistoryImage) +[func ImageExists(name: string) int](#ImageExists) + [func ImportImage(source: string, reference: string, message: string, changes: []string) string](#ImportImage) [func InspectContainer(name: string) string](#InspectContainer) @@ -57,6 +67,10 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListContainerChanges(name: string) ContainerChanges](#ListContainerChanges) +[func ListContainerMounts() []string](#ListContainerMounts) + +[func ListContainerPorts(name: string) NotImplemented](#ListContainerPorts) + [func ListContainerProcesses(name: string, opts: []string) []string](#ListContainerProcesses) [func ListContainers() ListContainerData](#ListContainers) @@ -65,6 +79,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ListPods() ListPodData](#ListPods) +[func MountContainer(name: string) string](#MountContainer) + [func PauseContainer(name: string) string](#PauseContainer) [func PausePod(name: string) string](#PausePod) @@ -103,6 +119,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func TopPod() NotImplemented](#TopPod) +[func UnmountContainer(name: string, force: bool) ](#UnmountContainer) + [func UnpauseContainer(name: string) string](#UnpauseContainer) [func UnpausePod(name: string) string](#UnpausePod) @@ -165,6 +183,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type PodmanInfo](#PodmanInfo) +[type Runlabel](#Runlabel) + [type Sockets](#Sockets) [type StringResponse](#StringResponse) @@ -211,6 +231,31 @@ attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOL container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, the resulting image's ID will be returned as a string. +### func ContainerCheckpoint +
+ +method ContainerCheckpoint(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), leaveRunning: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +ID. On successful checkpoint, the id of the checkpointed container is returned. +### func ContainerExists +
+ +method ContainerExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ContainerExists takes a full or partial container ID or name and returns an int as to +whether the container exists in local storage. A result of 0 means the container does +exists; whereas a result of 1 means it could not be found. +### func ContainerRestore +
+ +method ContainerRestore(name: [string](https://godoc.org/builtin#string), keep: [bool](https://godoc.org/builtin#bool), tcpEstablished: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+ContainerRestore restores a container that has been checkpointed. The container to be restored can +be identified by its name or full/partial container ID. A successful restore will result in the return +of the container's ID. +### func ContainerRunlabel +
+ +method ContainerRunlabel(runlabel: [Runlabel](#Runlabel))
+ContainerRunlabel runs executes a command as described by a given container image label. ### func CreateContainer
@@ -403,6 +448,13 @@ method HistoryImage(name: [string](https://godoc.org/builtin#string)) [ImageHist HistoryImage takes the name or ID of an image and returns information about its history and layers. The returned history is in the form of an array of ImageHistory structures. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error is returned. +### func ImageExists +
+ +method ImageExists(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)
+ImageExists talks a full or partial image ID or name and returns an int as to whether +the image exists in local storage. An int result of 0 means the image does exist in +local storage; whereas 1 indicates the image does not exists in local storage. ### func ImportImage
@@ -453,6 +505,17 @@ See also [StopPod](StopPod). method ListContainerChanges(name: [string](https://godoc.org/builtin#string)) [ContainerChanges](#ContainerChanges)
ListContainerChanges takes a name or ID of a container and returns changes between the container and its base image. It returns a struct of changed, deleted, and added path names. +### func ListContainerMounts +
+ +method ListContainerMounts() [[]string](#[]string)
+ListContainerMounts gathers all the mounted container mount points and returns them as an array +of strings +### func ListContainerPorts +
+ +method ListContainerPorts(name: [string](https://godoc.org/builtin#string)) [NotImplemented](#NotImplemented)
+This function is not implemented yet. ### func ListContainerProcesses
@@ -491,6 +554,12 @@ an image currently in storage. See also [InspectImage](InspectImage). method ListPods() [ListPodData](#ListPodData)
ListPods returns a list of pods in no particular order. They are returned as an array of ListPodData structs. See also [GetPod](#GetPod). +### func MountContainer +
+ +method MountContainer(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)
+MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +mount is returned as a string. ### func PauseContainer
@@ -696,6 +765,11 @@ be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, method TopPod() [NotImplemented](#NotImplemented)
This method has not been implemented yet. +### func UnmountContainer +
+ +method UnmountContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool))
+UnmountContainer umounts a container by its name or full/partial container ID. ### func UnpauseContainer
@@ -1293,6 +1367,33 @@ insecure_registries [[]string](#[]string) store [InfoStore](#InfoStore) podman [InfoPodmanBinary](#InfoPodmanBinary) +### type Runlabel + +Runlabel describes the required input for container runlabel + +image [string](https://godoc.org/builtin#string) + +authfile [string](https://godoc.org/builtin#string) + +certDir [string](https://godoc.org/builtin#string) + +creds [string](https://godoc.org/builtin#string) + +display [bool](https://godoc.org/builtin#bool) + +name [string](https://godoc.org/builtin#string) + +pull [bool](https://godoc.org/builtin#bool) + +signaturePolicyPath [string](https://godoc.org/builtin#string) + +tlsVerify [bool](https://godoc.org/builtin#bool) + +label [string](https://godoc.org/builtin#string) + +extraArgs [[]string](#[]string) + +opts [map[string]](#map[string]) ### type Sockets Sockets describes sockets location for a container @@ -1336,7 +1437,7 @@ ImageNotFound means the image could not be found by the provided name or ID in l NoContainerRunning means none of the containers requested are running in a command that requires a running container. ### type NoContainersInPod -NoContainersInPod means a pod has no containers on which to perform operation. It contains +NoContainersInPod means a pod has no containers on which to perform the operation. It contains the pod ID. ### type PodContainerError diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index e1dee1fb2..b0d87d0d9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -6,11 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +92,7 @@ func runlabelCmd(c *cli.Context) error { imageName string stdErr, stdOut io.Writer stdIn io.Reader - newImage *image.Image + extraArgs []string ) // Evil images could trick into recursively executing the runlabel @@ -124,6 +122,9 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("the display and quiet flags cannot be used together.") } + if len(args) > 2 { + extraArgs = args[2:] + } pull := c.Bool("pull") label := args[0] @@ -151,75 +152,24 @@ func runlabelCmd(c *cli.Context) error { stdIn = nil } - if pull { - var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) - if err != nil { - return err - } - registryCreds = creds - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - } - authfile := getAuthFile(c.String("authfile")) - - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false) - } else { - newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) - } - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: c.String("cert-dir"), + DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), } - runLabel, err := newImage.GetLabel(ctx, label) + authfile := getAuthFile(c.String("authfile")) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) if err != nil { return err } - - // If no label to execute, we return if runLabel == "" { return nil } - // The user provided extra arguments that need to be tacked onto the label's command - if len(args) > 2 { - runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " ")) - } - - cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name")) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) if err != nil { - return errors.Wrapf(err, "unable to generate command") - } - env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts) - env = append(env, "PODMAN_RUNLABEL_NESTED=1") - - envmap := envSliceToMap(env) - - envmapper := func(k string) string { - switch k { - case "OPT1": - return envmap["OPT1"] - case "OPT2": - return envmap["OPT2"] - case "OPT3": - return envmap["OPT3"] - } - return "" + return err } - - newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") - if !c.Bool("quiet") { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) if c.Bool("display") { @@ -228,12 +178,3 @@ func runlabelCmd(c *cli.Context) error { } return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } - -func envSliceToMap(env []string) map[string]string { - m := make(map[string]string) - for _, i := range env { - split := strings.Split(i, "=") - m[split[0]] = strings.Join(split[1:], " ") - } - return m -} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4404268d4..d0e892961 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -1,10 +1,15 @@ package shared import ( + "context" "encoding/json" "fmt" + "github.com/containers/image/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "io" "os" "path/filepath" "regexp" @@ -589,3 +594,79 @@ func portsToString(ports []ocicni.PortMapping) string { } return strings.Join(portDisplay, ", ") } + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// contruction of the runlabel output and environment variables +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, false) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // The user provided extra arguments that need to be tacked onto the label's command + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd = strings.Split(newS, " ") + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4a4a1854c..b081b60a3 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -371,6 +371,22 @@ type PodContainerErrorData ( reason: string ) +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + certDir: string, + creds: string, + display: bool, + name: string, + pull: bool, + signaturePolicyPath: string, + tlsVerify: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + # Ping provides a response for developers to ensure their varlink setup is working. # #### Example # ~~~ @@ -804,6 +820,42 @@ method TopPod() -> (notimplemented: NotImplemented) # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +method ImageExists(name: string) -> (exists: int) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +method ListContainerMounts() -> (mounts: []string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +method UnmountContainer(name: string, force: bool) -> () + +# This function is not implemented yet. +method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index f517e9b6e..07d981786 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -278,6 +278,18 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim return call.ReplyRestartContainer(ctr.ID()) } +// ContainerExists looks in local storage for the existence of a container +func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.LookupContainer(name) + if errors.Cause(err) == libpod.ErrNoSuchCtr { + return call.ReplyContainerExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerExists(0) +} + // KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1 // for the signal arg. func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { @@ -413,3 +425,40 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } return call.ReplyGetAttachSockets(s) } + +// ContainerCheckpoint ... +func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + KeepRunning: leaveRunning, + } + if err := ctr.Checkpoint(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerCheckpoint(ctr.ID()) +} + +// ContainerRestore ... +func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { + ctx := getContext() + ctr, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyContainerNotFound(name) + } + + options := libpod.ContainerCheckpointOptions{ + Keep: keep, + TCPEstablished: tcpEstablished, + } + if err := ctr.Restore(ctx, options); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRestore(ctr.ID()) +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 42e285b53..6d3f19422 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/containers/libpod/cmd/podman/shared" "io" + "os" "path/filepath" "strings" "time" @@ -19,6 +21,7 @@ import ( "github.com/containers/libpod/libpod/image" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" "github.com/docker/go-units" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" @@ -500,3 +503,45 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { } return call.ReplyPullImage(newImage.ID()) } + +// ImageExists returns bool as to whether the input image exists in local storage +func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { + _, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if errors.Cause(err) == libpod.ErrNoSuchImage { + return call.ReplyImageExists(1) + } + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyImageExists(0) +} + +// ContainerRunlabel ... +func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { + ctx := getContext() + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: input.CertDir, + DockerInsecureSkipTLSVerify: !input.TlsVerify, + } + + stdErr := os.Stderr + stdOut := os.Stdout + stdIn := os.Stdin + + runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil) + if err != nil { + return err + } + if runLabel == "" { + return nil + } + + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs) + if err != nil { + return err + } + if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyContainerRunlabel() +} diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go new file mode 100644 index 000000000..84e6b2709 --- /dev/null +++ b/pkg/varlinkapi/mount.go @@ -0,0 +1,49 @@ +package varlinkapi + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// ListContainerMounts ... +func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { + var mounts []string + allContainers, err := i.Runtime.GetAllContainers() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for _, container := range allContainers { + mounted, mountPoint, err := container.Mounted() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if mounted { + mounts = append(mounts, mountPoint) + } + } + return call.ReplyListContainerMounts(mounts) +} + +// MountContainer ... +func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + path, err := container.Mount() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyMountContainer(path) +} + +// UnmountContainer ... +func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { + container, err := i.Runtime.LookupContainer(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := container.Unmount(force); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyUnmountContainer() +} -- cgit v1.2.3-54-g00ecf From 318bf7017bcb82da9f73cfce9e3a963b61252788 Mon Sep 17 00:00:00 2001 From: baude Date: Sat, 1 Dec 2018 13:51:58 -0600 Subject: podman pod exists like containers and images, users would benefit from being able to check if a pod exists in local storage. if the pod exists, the return code is 0. if the pod does not exists, the return code is 1. Any other return code indicates a real errors, such as permissions or runtime. Signed-off-by: baude --- cmd/podman/exists.go | 37 +++++++++++++++++++++++++++++++++++++ cmd/podman/pod.go | 1 + completions/bash/podman | 8 ++++++++ docs/podman-pod-exists.1.md | 40 ++++++++++++++++++++++++++++++++++++++++ test/e2e/exists_test.go | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 docs/podman-pod-exists.1.md (limited to 'cmd') diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index 2f7b7c185..2e2559ec7 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -44,6 +44,23 @@ var ( } ) +var ( + podExistsDescription = ` + podman pod exists + + Check if a pod exists in local storage +` + + podExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a pod exists in local storage", + Description: podExistsDescription, + Action: podExistsCmd, + ArgsUsage: "POD-NAME", + OnUsageError: usageErrorHandler, + } +) + func imageExistsCmd(c *cli.Context) error { args := c.Args() if len(args) > 1 || len(args) < 1 { @@ -81,3 +98,23 @@ func containerExistsCmd(c *cli.Context) error { } return nil } + +func podExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one pod at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + if _, err := runtime.LookupPod(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchPod { + os.Exit(1) + } + return err + } + return nil +} diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 0c6ec5e8c..a30361134 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -11,6 +11,7 @@ Pods are a group of one or more containers sharing the same network, pid and ipc ` podSubCommands = []cli.Command{ podCreateCommand, + podExistsCommand, podInspectCommand, podKillCommand, podPauseCommand, diff --git a/completions/bash/podman b/completions/bash/podman index 3cccf2192..9518cfa22 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2235,6 +2235,14 @@ _podman_container_exists() { " } +_podman_pod_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + _podman_image_exists() { local options_with_args=" " diff --git a/docs/podman-pod-exists.1.md b/docs/podman-pod-exists.1.md new file mode 100644 index 000000000..8fb2fc90e --- /dev/null +++ b/docs/podman-pod-exists.1.md @@ -0,0 +1,40 @@ +% podman-pod-exits(1) Podman Man Pages +% Brent Baude +% December 2018 +# NAME +podman-pod-exists- Check if a pod exists in local storage + +# SYNOPSIS +**podman pod exists** +[**-h**|**--help**] +POD + +# DESCRIPTION +**podman pod exists** checks if a pod exists in local storage. The **ID** or **Name** +of the pod may be used as input. Podman will return an exit code +of `0` when the pod is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if a pod called `web` exists in local storage (the pod does actually exist). +``` +$ sudo podman pod exists web +$ echo $? +0 +$ +``` + +Check if a pod called `backend` exists in local storage (the pod does not actually exist). +``` +$ sudo podman pod exists backend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman-pod(1), podman(1) + +# HISTORY +December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go index 9165e8902..d9652de4b 100644 --- a/test/e2e/exists_test.go +++ b/test/e2e/exists_test.go @@ -82,4 +82,36 @@ var _ = Describe("Podman image|container exists", func() { Expect(session.ExitCode()).To(Equal(1)) }) + It("podman pod exists in local storage by name", func() { + setup, rc, _ := podmanTest.CreatePod("foobar") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod exists in local storage by container ID", func() { + setup, rc, podID := podmanTest.CreatePod("") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", podID}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod exists in local storage by short container ID", func() { + setup, rc, podID := podmanTest.CreatePod("") + setup.WaitWithDefaultTimeout() + Expect(rc).To(Equal(0)) + + session := podmanTest.Podman([]string{"pod", "exists", podID[0:12]}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod does not exist in local storage", func() { + session := podmanTest.Podman([]string{"pod", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) }) -- cgit v1.2.3-54-g00ecf From a4b483c8484bb6fb9ae487264bccc663f007e711 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 2 Dec 2018 21:22:08 -0800 Subject: libpod/container_internal: Deprecate implicit hook directories Part of the motivation for 800eb863 (Hooks supports two directories, process default and override, 2018-09-17, #1487) was [1]: > We only use this for override. The reason this was caught is people > are trying to get hooks to work with CoreOS. You are not allowed to > write to /usr/share... on CoreOS, so they wanted podman to also look > at /etc, where users and third parties can write. But we'd also been disabling hooks completely for rootless users. And even for root users, the override logic was tricky when folks actually had content in both directories. For example, if you wanted to disable a hook from the default directory, you'd have to add a no-op hook to the override directory. Also, the previous implementation failed to handle the case where there hooks defined in the override directory but the default directory did not exist: $ podman version Version: 0.11.2-dev Go Version: go1.10.3 Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214" Built: Sun Dec 2 21:30:06 2018 OS/Arch: linux/amd64 $ ls -l /etc/containers/oci/hooks.d/test.json -rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json $ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d" time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)" With this commit: $ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d" time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d" time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json" time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]" time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory" time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\"" (I'd setup the hook to error out). You can see that it's silenly ignoring the ENOENT for /usr/share/containers/oci/hooks.d and continuing on to load hooks from /etc/containers/oci/hooks.d. When it loads the hook, it also logs a warning-level message suggesting that callers explicitly configure their hook directories. That will help consumers migrate, so we can drop the implicit hook directories in some future release. When folks *do* explicitly configure hook directories (via the newly-public --hooks-dir and hooks_dir options), we error out if they're missing: $ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container' error setting up OCI Hooks: open /does/not/exist: no such file or directory I've dropped the trailing "path" from the old, hidden --hooks-dir-path and hooks_dir_path because I think "dir(ectory)" is already enough context for "we expect a path argument". I consider this name change non-breaking because the old forms were undocumented. Coming back to rootless users, I've enabled hooks now. I expect they were previously disabled because users had no way to avoid /usr/share/containers/oci/hooks.d which might contain hooks that required root permissions. But now rootless users will have to explicitly configure hook directories, and since their default config is from ~/.config/containers/libpod.conf, it's a misconfiguration if it contains hooks_dir entries which point at directories with hooks that require root access. We error out so they can fix their libpod.conf. [1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355 Signed-off-by: W. Trevor King --- cmd/podman/libpodruntime/runtime.go | 4 ++-- cmd/podman/main.go | 9 +++---- docs/libpod.conf.5.md | 12 ++++++++++ docs/podman.1.md | 24 +++++++++---------- libpod/container_internal.go | 48 ++++++++++++++++++++++--------------- libpod/container_internal_linux.go | 6 ++--- libpod/options.go | 15 ++++++------ libpod/runtime.go | 10 ++++---- libpod/testdata/config.toml | 2 +- test/e2e/run_test.go | 2 +- 10 files changed, 73 insertions(+), 59 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index a4b3581be..f69eaf3a4 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -90,8 +90,8 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions if c.GlobalIsSet("default-mounts-file") { options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file"))) } - if c.GlobalIsSet("hooks-dir-path") { - options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path"))) + if c.GlobalIsSet("hooks-dir") { + options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...)) } // TODO flag to set CNI plugins dir? diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 6be192593..bcae04575 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -8,7 +8,6 @@ import ( "syscall" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/hooks" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/version" @@ -206,11 +205,9 @@ func main() { Usage: "path to default mounts file", Hidden: true, }, - cli.StringFlag{ - Name: "hooks-dir-path", - Usage: "set the OCI hooks directory path", - Value: hooks.DefaultDir, - Hidden: true, + cli.StringSliceFlag{ + Name: "hooks-dir", + Usage: "set the OCI hooks directory path (may be set multiple times)", }, cli.IntFlag{ Name: "max-workers", diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index 198e927ee..d63baeb88 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -24,6 +24,18 @@ libpod to manage containers. **cgroup_manager**="" Specify the CGroup Manager to use; valid values are "systemd" and "cgroupfs" +**hooks_dir**=["*path*", ...] + + Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. + + Paths listed later in the array higher precedence (`oci-hooks(5)` discusses directory precedence). + + For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. + + For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. + + If `hooks_dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `hooks_dir`. + **static_dir**="" Directory for persistent libpod files (database, etc) By default this will be configured relative to where containers/storage diff --git a/docs/podman.1.md b/docs/podman.1.md index b7433d850..bde349e6f 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -31,6 +31,18 @@ CGroup manager to use for container cgroups. Supported values are cgroupfs or sy Path to where the cpu performance results should be written +**--hooks-dir**=**path** + +Each `*.json` file in the path configures a hook for Podman containers. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. + +This option may be set multiple times; paths from later options have higher precedence (`oci-hooks(5)` discusses directory precedence). + +For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. + +For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. + +If `--hooks-dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `--hooks-dir`. + **--log-level** Log messages above specified level: debug, info, warn, error (default), fatal or panic @@ -161,18 +173,6 @@ the exit codes follow the `chroot` standard, see below: The mounts.conf file specifies volume mount directories that are automatically mounted inside containers when executing the `podman run` or `podman start` commands. When Podman runs in rootless mode, the file `$HOME/.config/containers/mounts.conf` is also used. Please refer to containers-mounts.conf(5) for further details. -**OCI hooks JSON** (`/etc/containers/oci/hooks.d/*.json`, `/usr/share/containers/oci/hooks.d/*.json`) - - Each `*.json` file in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` configures a hook for Podman containers, with `/etc/containers/oci/hooks.d` having higher precedence. For more details on the syntax of the JSON files and the semantics of hook injection, see `oci-hooks(5)`. - - Podman and libpod currently support both the 1.0.0 and 0.1.0 hook schemas, although the 0.1.0 schema is deprecated. - - For the annotation conditions, libpod uses any annotations set in the generated OCI configuration. - - For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. - - Hooks are not used when running in rootless mode. - **policy.json** (`/etc/containers/policy.json`) Signature verification policy files are used to specify policy, e.g. trusted keys, applicable when deciding whether to accept an image, or individual signatures of that image, as valid. diff --git a/libpod/container_internal.go b/libpod/container_internal.go index e31a8099c..934ad7a22 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1168,10 +1168,6 @@ func (c *Container) saveSpec(spec *spec.Spec) error { } func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) { - if len(c.runtime.config.HooksDir) == 0 { - return nil, nil - } - var locale string var ok bool for _, envVar := range []string{ @@ -1199,25 +1195,39 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten } } - allHooks := make(map[string][]spec.Hook) - for _, hDir := range c.runtime.config.HooksDir { - manager, err := hooks.New(ctx, []string{hDir}, []string{"poststop"}, lang) - if err != nil { - if c.runtime.config.HooksDirNotExistFatal || !os.IsNotExist(err) { - return nil, err - } - logrus.Warnf("failed to load hooks: %q", err) + if c.runtime.config.HooksDir == nil { + if rootless.IsRootless() { return nil, nil } - hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) - if err != nil { - return nil, err - } - for i, hook := range hooks { - allHooks[i] = hook + allHooks := make(map[string][]spec.Hook) + for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} { + manager, err := hooks.New(ctx, []string{hDir}, []string{"poststop"}, lang) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) + if err != nil { + return nil, err + } + if len(hooks) > 0 || config.Hooks != nil { + logrus.Warnf("implicit hook directories are deprecated; set --hooks-dir=%q explicitly to continue to load hooks from this directory", hDir) + } + for i, hook := range hooks { + allHooks[i] = hook + } } + return allHooks, nil } - return allHooks, nil + + manager, err := hooks.New(ctx, c.runtime.config.HooksDir, []string{"poststop"}, lang) + if err != nil { + return nil, err + } + + return manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) } // mount mounts the container's root filesystem diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 8861d7728..780bf5279 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -224,10 +224,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } - if !rootless.IsRootless() { - if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { - return nil, errors.Wrapf(err, "error setting up OCI Hooks") - } + if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { + return nil, errors.Wrapf(err, "error setting up OCI Hooks") } // Bind builtin image volumes diff --git a/libpod/options.go b/libpod/options.go index 7f4e3ac6b..e1d0b5007 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -179,21 +179,20 @@ func WithStaticDir(dir string) RuntimeOption { } } -// WithHooksDir sets the directory to look for OCI runtime hooks config. -// Note we are not saving this in database, since this is really just for used -// for testing. -func WithHooksDir(hooksDir string) RuntimeOption { +// WithHooksDir sets the directories to look for OCI runtime hook configuration. +func WithHooksDir(hooksDirs ...string) RuntimeOption { return func(rt *Runtime) error { if rt.valid { return ErrRuntimeFinalized } - if hooksDir == "" { - return errors.Wrap(ErrInvalidArg, "empty-string hook directories are not supported") + for _, hooksDir := range hooksDirs { + if hooksDir == "" { + return errors.Wrap(ErrInvalidArg, "empty-string hook directories are not supported") + } } - rt.config.HooksDir = []string{hooksDir} - rt.config.HooksDirNotExistFatal = true + rt.config.HooksDir = hooksDirs return nil } } diff --git a/libpod/runtime.go b/libpod/runtime.go index 9feae03fc..e043715b1 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -12,7 +12,6 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/firewall" - "github.com/containers/libpod/pkg/hooks" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" @@ -141,11 +140,11 @@ type RuntimeConfig struct { // CNIDefaultNetwork is the network name of the default CNI network // to attach pods to CNIDefaultNetwork string `toml:"cni_default_network,omitempty"` - // HooksDir Path to the directory containing hooks configuration files + // HooksDir holds paths to the directories containing hooks + // configuration files. When the same filename is present in in + // multiple directories, the file in the directory listed last in + // this slice takes precedence. HooksDir []string `toml:"hooks_dir"` - // HooksDirNotExistFatal switches between fatal errors and non-fatal - // warnings if the configured HooksDir does not exist. - HooksDirNotExistFatal bool `toml:"hooks_dir_not_exist_fatal"` // DefaultMountsFile is the path to the default mounts file for testing // purposes only DefaultMountsFile string `toml:"-"` @@ -203,7 +202,6 @@ var ( "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, CgroupManager: SystemdCgroupsManager, - HooksDir: []string{hooks.DefaultDir, hooks.OverrideDir}, StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), TmpDir: "", MaxLogSize: -1, diff --git a/libpod/testdata/config.toml b/libpod/testdata/config.toml index e19d36017..1d78f2083 100644 --- a/libpod/testdata/config.toml +++ b/libpod/testdata/config.toml @@ -14,7 +14,7 @@ seccomp_profile = "/etc/crio/seccomp.json" apparmor_profile = "crio-default" cgroup_manager = "cgroupfs" - hooks_dir_path = "/usr/share/containers/oci/hooks.d" + hooks_dir = ["/usr/share/containers/oci/hooks.d"] pids_limit = 2048 container_exits_dir = "/var/run/podman/exits" [crio.image] diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 38504828b..b04116218 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -336,7 +336,7 @@ var _ = Describe("Podman run", func() { hooksDir := tempdir + "/hooks" os.Mkdir(hooksDir, 0755) fileutils.CopyFile("hooks/hooks.json", hooksDir) - os.Setenv("HOOK_OPTION", fmt.Sprintf("--hooks-dir-path=%s", hooksDir)) + os.Setenv("HOOK_OPTION", fmt.Sprintf("--hooks-dir=%s", hooksDir)) os.Remove(hcheck) session := podmanTest.Podman([]string{"run", ALPINE, "ls"}) session.Wait(10) -- cgit v1.2.3-54-g00ecf From 9c359a31d542074ff686a2f9ad29deee73e92d79 Mon Sep 17 00:00:00 2001 From: baude Date: Fri, 30 Nov 2018 10:23:28 -0600 Subject: create pod on the fly when a user specifies --pod to podman create|run, we should create that pod automatically. the port bindings from the container are then inherited by the infra container. this signicantly improves the workflow of running containers inside pods with podman. the user is still encouraged to use podman pod create to have more granular control of the pod create options. Signed-off-by: baude --- cmd/podman/create.go | 65 +++++++++++++++++++++++++++++++++++++----------- docs/podman-create.1.md | 3 ++- docs/podman-run.1.md | 3 ++- pkg/spec/createconfig.go | 7 +++++- test/e2e/create_test.go | 11 ++++++++ test/e2e/run_test.go | 11 ++++++++ 6 files changed, 82 insertions(+), 18 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bcf830c7c..c40650f83 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" @@ -375,8 +376,8 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string) (map[string]string, error) { - pod, err := runtime.LookupPod(c.String("pod")) +func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { + pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err } @@ -409,6 +410,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 blkioWeight uint16 + namespaces map[string]string ) idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) if err != nil { @@ -492,12 +494,21 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) + if err != nil { + return nil, err + } + } + // Kernel Namespaces // TODO Fix handling of namespace from pod // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated - namespaces := map[string]string{ + namespaces = map[string]string{ "pid": c.String("pid"), "net": c.String("net"), "ipc": c.String("ipc"), @@ -505,8 +516,41 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim "uts": c.String("uts"), } + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } if c.IsSet("pod") { - namespaces, err = configurePod(c, runtime, namespaces) + if strings.HasPrefix(originalPodName, "new:") { + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, err = configurePod(c, runtime, namespaces, podName) if err != nil { return nil, err } @@ -535,7 +579,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Make sure if network is set to container namespace, port binding is not also being asked for netMode := ns.NetworkMode(namespaces["net"]) if netMode.IsContainer() { - if len(c.StringSlice("publish")) > 0 || c.Bool("publish-all") { + if len(portBindings) > 0 { return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") } } @@ -644,15 +688,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") } - // EXPOSED PORTS - var portBindings map[nat.Port][]nat.PortBinding - if data != nil { - portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) - if err != nil { - return nil, err - } - } - // SHM Size shmSize, err := units.FromHumanSize(c.String("shm-size")) if err != nil { @@ -746,7 +781,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim NetMode: netMode, UtsMode: utsMode, PidMode: pidMode, - Pod: c.String("pod"), + Pod: podName, Privileged: c.Bool("privileged"), Publish: c.StringSlice("publish"), PublishAll: c.Bool("publish-all"), diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 6fbdd0d03..f1409a554 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -455,7 +455,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index a6761a393..5917f6f7a 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -439,7 +439,8 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain **--pod**="" -Run container in an existing pod +Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`. +To make a pod with more granular options, use the `podman pod create` command before creating a container. **--privileged**=*true*|*false* diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index a0fd40318..25f8cd7a1 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -496,8 +496,13 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib // CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { + return NatToOCIPortBindings(c.PortBindings) +} + +// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice +func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { var portBindings []ocicni.PortMapping - for containerPb, hostPb := range c.PortBindings { + for containerPb, hostPb := range ports { var pm ocicni.PortMapping pm.ContainerPort = int32(containerPb.Int()) for _, i := range hostPb { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 366a58f02..684a7cd88 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -189,4 +189,15 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test rw,nosuid,nodev,noexec,relatime - tmpfs")) }) + + It("podman create --pod automatically", func() { + session := podmanTest.Podman([]string{"create", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 38504828b..aaee7fa53 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -628,4 +628,15 @@ USER mail` isSharedOnly := !strings.Contains(shared[0], "shared,") Expect(isSharedOnly).Should(BeTrue()) }) + + It("podman run --pod automatically", func() { + session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString("foobar") + Expect(match).To(BeTrue()) + }) }) -- cgit v1.2.3-54-g00ecf From 41a7bd9c9e60283d893d9fca559d57338581058d Mon Sep 17 00:00:00 2001 From: baude Date: Tue, 4 Dec 2018 10:14:13 -0600 Subject: correct algorithm for deleting all images when deleting all images, we need to iterate all the images deleting on those who dont have children first. And then reiterate until they are all gone. This resolves #1926 Signed-off-by: baude --- cmd/podman/rmi.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index c0a0d69df..0f4f8765b 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -91,8 +91,23 @@ func rmiCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to query local images") } - for _, i := range imagesToDelete { - removeImage(i) + lastNumberofImages := 0 + for len(imagesToDelete) > 0 { + if lastNumberofImages == len(imagesToDelete) { + return errors.New("unable to delete all images; re-run the rmi command again.") + } + for _, i := range imagesToDelete { + isParent, err := i.IsParent() + if err != nil { + return err + } + if isParent { + continue + } + removeImage(i) + } + lastNumberofImages = len(imagesToDelete) + imagesToDelete, err = runtime.ImageRuntime().GetImages() } } else { // Create image.image objects for deletion from user input. -- cgit v1.2.3-54-g00ecf