diff options
53 files changed, 711 insertions, 1190 deletions
@@ -5,6 +5,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func BuildImage(build: BuildInfo) MoreResponse](#BuildImage) +[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap) + [func Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) string](#Commit) [func ContainerArtifacts(name: string, artifactName: string) string](#ContainerArtifacts) @@ -57,6 +59,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func GetInfo() PodmanInfo](#GetInfo) +[func GetLayersMapWithImageInfo() string](#GetLayersMapWithImageInfo) + [func GetPod(name: string) ListPodData](#GetPod) [func GetPodStats(name: string) string, ContainerStats](#GetPodStats) @@ -259,6 +263,11 @@ method BuildImage(build: [BuildInfo](#BuildInfo)) [MoreResponse](#MoreResponse)< BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the 'dockerfile' and 'tags' options in the BuildInfo structure. It will return a [MoreResponse](#MoreResponse) structure that contains the build logs and resulting image ID. +### <a name="BuildImageHierarchyMap"></a>func BuildImageHierarchyMap +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method BuildImageHierarchyMap(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +BuildImageHierarchyMap is for the development of Podman and should not be used. ### <a name="Commit"></a>func Commit <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -396,7 +405,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div> - +Diff returns a diff between libpod objects ### <a name="ExportContainer"></a>func ExportContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -520,6 +529,11 @@ If the image caGetImage returns be found, [ImageNotFound](#ImageNotFound) will b method GetInfo() [PodmanInfo](#PodmanInfo)</div> GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, build information of Podman, and system-wide registries. +### <a name="GetLayersMapWithImageInfo"></a>func GetLayersMapWithImageInfo +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetLayersMapWithImageInfo() [string](https://godoc.org/builtin#string)</div> +GetLayersMapWithImageInfo is for the development of Podman and should not be used. ### <a name="GetPod"></a>func GetPod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1507,6 +1521,8 @@ containers [int](https://godoc.org/builtin#int) labels [map[string]](#map[string]) isParent [bool](https://godoc.org/builtin#bool) + +topLayer [string](https://godoc.org/builtin#string) ### <a name="ImageHistory"></a>type ImageHistory ImageHistory describes the returned structure from ImageHistory. diff --git a/Dockerfile b/Dockerfile index e0da63ac3..83cd3fccd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,6 +38,8 @@ RUN apt-get update && apt-get install -y \ socat \ lsof \ xz-utils \ + unzip \ + python3-yaml \ --no-install-recommends \ && apt-get clean diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 18fb2cb73..a0dd46260 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -1,10 +1,8 @@ package main import ( - "io/ioutil" "os" "path/filepath" - "strconv" "strings" "github.com/containers/buildah/pkg/chrootuser" @@ -12,7 +10,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" @@ -58,9 +55,6 @@ func cpCmd(c *cliconfig.CpValues) error { if len(args) != 2 { return errors.Errorf("you must provide a source path and a destination path") } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -90,34 +84,6 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin ctr = destCtr } - if os.Geteuid() != 0 { - s, err := ctr.State() - if err != nil { - return err - } - var became bool - var ret int - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - became, ret, err = rootless.BecomeRootInUserNS() - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - mountPoint, err := ctr.Mount() if err != nil { return err diff --git a/cmd/podman/create.go b/cmd/podman/create.go index bceb606f6..984323653 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -2,12 +2,10 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/pkg/rootless" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -54,10 +52,6 @@ func createCmd(c *cliconfig.CreateValues) error { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index fc1c76e9f..f720a9aff 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -10,7 +10,6 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -67,7 +66,6 @@ func execCmd(c *cliconfig.ExecValues) error { if c.Latest { argStart = 0 } - rootless.SetSkipStorageSetup(true) cmd := args[argStart:] runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { @@ -107,32 +105,6 @@ func execCmd(c *cliconfig.ExecValues) error { } - if os.Geteuid() != 0 { - var became bool - var ret int - - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err == nil { - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err = rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } else { - pid, err := ctr.PID() - if err != nil { - return err - } - became, ret, err = rootless.JoinNS(uint(pid), c.PreserveFDs) - } - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - // ENVIRONMENT VARIABLES env := map[string]string{} diff --git a/cmd/podman/export.go b/cmd/podman/export.go index 92633facd..db031aaf2 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -41,10 +40,6 @@ func init() { // exportCmd saves a container to a tarball on disk func exportCmd(c *cliconfig.ExportValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 2c1e13eaf..6019fbfec 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -4,12 +4,10 @@ import ( "fmt" "reflect" - "github.com/containers/libpod/pkg/adapter" - "github.com/opentracing/opentracing-go" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/adapter" "github.com/docker/docker/pkg/signal" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -63,7 +61,6 @@ func killCmd(c *cliconfig.KillValues) error { return err } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/main.go b/cmd/podman/main.go index cff9a6961..1ba58d1f3 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -3,13 +3,16 @@ package main import ( "context" "io" + "io/ioutil" "log/syslog" "os" "runtime/pprof" + "strconv" "strings" "syscall" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" @@ -60,36 +63,6 @@ var mainCommands = []*cobra.Command{ systemCommand.Command, } -var cmdsNotRequiringRootless = map[*cobra.Command]bool{ - _versionCommand: true, - _createCommand: true, - _execCommand: true, - _cpCommand: true, - _exportCommand: true, - //// `info` must be executed in an user namespace. - //// If this change, please also update libpod.refreshRootless() - _loginCommand: true, - _logoutCommand: true, - _mountCommand: true, - _killCommand: true, - _pauseCommand: true, - _podRmCommand: true, - _podKillCommand: true, - _podRestartCommand: true, - _podStatsCommand: true, - _podStopCommand: true, - _podTopCommand: true, - _restartCommand: true, - &_psCommand: true, - _rmCommand: true, - _runCommand: true, - _unpauseCommand: true, - _searchCommand: true, - _statsCommand: true, - _stopCommand: true, - _topCommand: true, -} - var rootCmd = &cobra.Command{ Use: "podman", Long: "manage pods and images", @@ -153,18 +126,52 @@ func before(cmd *cobra.Command, args []string) error { logrus.Errorf(err.Error()) os.Exit(1) } - if rootless.IsRootless() { - notRequireRootless := cmdsNotRequiringRootless[cmd] - if !notRequireRootless && !strings.HasPrefix(cmd.Use, "help") { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) + if os.Geteuid() != 0 && cmd != _searchCommand && cmd != _versionCommand && !strings.HasPrefix(cmd.Use, "help") { + podmanCmd := cliconfig.PodmanCommand{ + cmd, + args, + MainGlobalOpts, + } + runtime, err := libpodruntime.GetRuntime(&podmanCmd) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ctrs, err := runtime.GetRunningContainers() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + var became bool + var ret int + if len(ctrs) == 0 { + became, ret, err = rootless.BecomeRootInUserNS() + } else { + for _, ctr := range ctrs { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + became, ret, err = rootless.JoinUserAndMountNS(uint(conmonPid)) + if err == nil { + break + } } } + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + if became { + os.Exit(ret) + } } if MainGlobalOpts.Syslog { diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index 138548097..a70684a39 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -60,10 +60,6 @@ type jsonMountPoint struct { } func mountCmd(c *cliconfig.MountValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 9a9c7a702..2d9bca21d 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,12 +1,7 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -39,48 +34,6 @@ var podSubCommands = []*cobra.Command{ _podUnpauseCommand, } -func joinPodNS(runtime *adapter.LocalRuntime, all, latest bool, inputArgs []string) ([]string, bool, bool, error) { - if rootless.IsRootless() { - if os.Geteuid() == 0 { - return []string{rootless.Argument()}, false, false, nil - } else { - var err error - var pods []*adapter.Pod - if all { - pods, err = runtime.GetAllPods() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get pods") - } - } else if latest { - pod, err := runtime.GetLatestPod() - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to get latest pod") - } - pods = append(pods, pod) - } else { - for _, i := range inputArgs { - pod, err := runtime.LookupPod(i) - if err != nil { - return nil, false, false, errors.Wrapf(err, "unable to lookup pod %s", i) - } - pods = append(pods, pod) - } - } - for _, p := range pods { - _, ret, err := runtime.JoinOrCreateRootlessPod(p) - if err != nil { - return nil, false, false, err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - } - return inputArgs, all, latest, nil -} - func init() { podCommand.AddCommand(podSubCommands...) podCommand.SetHelpTemplate(HelpTemplate()) diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index c538674a4..ebd7db762 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -49,7 +48,6 @@ func init() { // podKillCmd kills one or more pods with a signal func podKillCmd(c *cliconfig.PodKillValues) error { - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index 9c8d28424..0765b98db 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { func podRestartCmd(c *cliconfig.PodRestartValues) error { var lastError error - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) for _, p := range restartIDs { diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index 735676f8a..cd9f23fe1 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,23 +46,12 @@ func init() { // podRmCmd deletes pods func podRmCmd(c *cliconfig.PodRmValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podRmIds, podRmErrors := runtime.RemovePods(getContext(), c) for _, p := range podRmIds { fmt.Println(p) diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 754a3a7db..f1b0ac51f 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,11 +2,9 @@ package main import ( "fmt" - "os" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -48,24 +46,12 @@ func init() { } func podStopCmd(c *cliconfig.PodStopValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if rootless.IsRootless() { - var err error - c.InputArgs, c.All, c.Latest, err = joinPodNS(runtime, c.All, c.Latest, c.InputArgs) - if err != nil { - return err - } - } - podStopIds, podStopErrors := runtime.StopPods(getContext(), c) for _, p := range podStopIds { fmt.Println(p) diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index f65d66df6..0d74dc3d6 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -54,10 +53,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { ) args := c.InputArgs - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { @@ -83,26 +78,6 @@ func podTopCmd(c *cliconfig.PodTopValues) error { descriptors = args[1:] } - if os.Geteuid() != 0 { - var pod *adapter.Pod - var err error - if c.Latest { - pod, err = runtime.GetLatestPod() - } else { - pod, err = runtime.LookupPod(c.InputArgs[0]) - } - if err != nil { - return errors.Wrapf(err, "unable to lookup requested container") - } - became, ret, err := runtime.JoinOrCreateRootlessPod(pod) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) psOutput, err := runtime.PodTop(c, descriptors) if err != nil { diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 71c8dee97..759a03b86 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -17,7 +17,6 @@ import ( "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/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -202,9 +201,6 @@ func init() { } func psCmd(c *cliconfig.PsValues) error { - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } if c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "psCmd") defer span.Finish() diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index e6a6d8434..1553ab805 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,13 +1,10 @@ package main import ( - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" "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/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -57,19 +54,6 @@ func restartCmd(c *cliconfig.RestartValues) error { restartContainers []*libpod.Container ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } - if rootless.IsRootless() { - // If we are in the re-execed rootless environment, - // override the arg to deal only with one container. - if os.Geteuid() == 0 { - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } - } - args := c.InputArgs runOnly := c.Running all := c.All @@ -115,20 +99,6 @@ func restartCmd(c *cliconfig.RestartValues) error { } } - if os.Geteuid() != 0 { - // In rootless mode we can deal with one container at at time. - for _, c := range restartContainers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(0) - } - maxWorkers := shared.Parallelize("restart") if c.GlobalIsSet("max-workers") { maxWorkers = c.GlobalFlags.MaxWorks diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 253771e14..52e281402 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,16 +2,12 @@ package main import ( "fmt" - "io/ioutil" - "os" - "strconv" "github.com/containers/libpod/cmd/podman/cliconfig" "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" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -52,39 +48,11 @@ func init() { markFlagHiddenForRemoteClient("latest", flags) } -func joinContainerOrCreateRootlessUserNS(runtime *libpod.Runtime, ctr *libpod.Container) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - s, err := ctr.State() - if err != nil { - return false, -1, err - } - opts := rootless.Opts{ - Argument: ctr.ID(), - } - if s == libpod.ContainerStateRunning || s == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // saveCmd saves the image to either docker-archive or oci func rmCmd(c *cliconfig.RmValues) error { var ( deleteFuncs []shared.ParallelWorkerInput ) - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } ctx := getContext() runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) @@ -93,58 +61,6 @@ func rmCmd(c *cliconfig.RmValues) error { } defer runtime.Shutdown(false) - if rootless.IsRootless() { - // When running in rootless mode we cannot manage different containers and - // user namespaces from the same context, so be sure to re-exec once for each - // container we are dealing with. - // What we do is to first collect all the containers we want to delete, then - // we re-exec in each of the container namespaces and from there remove the single - // container. - var container *libpod.Container - if os.Geteuid() == 0 { - // We are in the namespace, override InputArgs with the single - // argument that was passed down to us. - c.All = false - c.Latest = false - c.InputArgs = []string{rootless.Argument()} - } else { - exitCode = 0 - var containers []*libpod.Container - if c.All { - containers, err = runtime.GetContainers() - } else if c.Latest { - container, err = runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest pod") - } - containers = append(containers, container) - } else { - for _, c := range c.InputArgs { - container, err = runtime.LookupContainer(c) - if err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { - exitCode = 1 - continue - } - return err - } - containers = append(containers, container) - } - } - // Now we really delete the containers. - for _, c := range containers { - _, ret, err := joinContainerOrCreateRootlessUserNS(runtime, c) - if err != nil { - return err - } - if ret != 0 { - os.Exit(ret) - } - } - os.Exit(exitCode) - } - } - failureCnt := 0 delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3c26e98c1..4bd469106 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -12,7 +12,6 @@ import ( "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" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -57,9 +56,6 @@ func runCmd(c *cliconfig.RunValues) error { if err := createInit(&c.PodmanCommand); err != nil { return err } - if os.Geteuid() != 0 { - rootless.SetSkipStorageSetup(true) - } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index a896f1dc7..0bf9cb4d9 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/sysinfo" "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -16,8 +17,9 @@ import ( ) var ( - cmd = []string{"podman", "test", "alpine"} - CLI *cliconfig.PodmanCommand + sysInfo = sysinfo.New(true) + cmd = []string{"podman", "test", "alpine"} + CLI *cliconfig.PodmanCommand ) // generates a mocked ImageData structure based on alpine @@ -100,6 +102,9 @@ func TestPIDsLimit(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.PidsLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--pids-limit", "22"} a := createCLI(args) a.InputArgs = args @@ -119,6 +124,9 @@ func TestBLKIOWeightDevice(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.BlkioWeightDevice { + t.Skip("running test not supported by the host system") + } args := []string{"--blkio-weight-device", "/dev/zero:100"} a := createCLI(args) a.InputArgs = args @@ -137,6 +145,9 @@ func TestMemorySwap(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } + if !sysInfo.SwapLimit { + t.Skip("running test not supported by the host system") + } args := []string{"--memory-swap", "45m", "--memory", "40m"} a := createCLI(args) a.InputArgs = args diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index d927e5bf6..cd82e4f1c 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strconv" @@ -75,7 +74,8 @@ func CreateContainer(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l imageName := "" var data *inspect.ImageData = nil - if rootfs == "" && !rootless.SkipStorageSetup() { + // Set the storage if we are running as euid == 0 and there is no rootfs specified + if rootfs == "" && os.Geteuid() == 0 { var writer io.Writer if !c.Bool("quiet") { writer = os.Stderr @@ -758,71 +758,6 @@ type namespace interface { Container() string } -func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *libpod.Runtime) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - - if createConfig.Pod != "" { - pod, err := runtime.LookupPod(createConfig.Pod) - if err != nil { - return false, -1, err - } - inspect, err := pod.Inspect() - for _, ctr := range inspect.Containers { - prevCtr, err := runtime.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - } - } - - namespacesStr := []string{string(createConfig.IpcMode), string(createConfig.NetMode), string(createConfig.UsernsMode), string(createConfig.PidMode), string(createConfig.UtsMode)} - for _, i := range namespacesStr { - if cc.IsNS(i) { - return rootless.JoinNSPath(cc.NS(i)) - } - } - - namespaces := []namespace{createConfig.IpcMode, createConfig.NetMode, createConfig.UsernsMode, createConfig.PidMode, createConfig.UtsMode} - for _, i := range namespaces { - if i.IsContainer() { - ctr, err := runtime.LookupContainer(i.Container()) - if err != nil { - return false, -1, err - } - pid, err := ctr.PID() - if err != nil { - return false, -1, err - } - if pid == 0 { - if createConfig.Pod != "" { - continue - } - return false, -1, errors.Errorf("dependency container %s is not running", ctr.ID()) - } - return rootless.JoinNS(uint(pid), 0) - } - } - return rootless.BecomeRootInUserNS() -} - func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) if err != nil { @@ -833,13 +768,6 @@ func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateC if err != nil { return nil, err } - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) - if err != nil { - return nil, err - } - if became { - os.Exit(ret) - } ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 2a1470ad0..e27be64f6 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -7,7 +7,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/rootless" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -59,7 +58,6 @@ func stopCmd(c *cliconfig.StopValues) error { defer span.Finish() } - rootless.SetSkipStorageSetup(true) runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 2512631c1..5d394d2d6 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -77,7 +76,6 @@ func topCmd(c *cliconfig.TopValues) error { return errors.Errorf("you must provide the name or id of a running container") } - rootless.SetSkipStorageSetup(true) runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") @@ -104,18 +102,6 @@ func topCmd(c *cliconfig.TopValues) error { if conStat != libpod.ContainerStateRunning { return errors.Errorf("top can only be used on running containers") } - - pid, err := container.PID() - if err != nil { - return err - } - became, ret, err := rootless.JoinNS(uint(pid), 0) - if err != nil { - return err - } - if became { - os.Exit(ret) - } psOutput, err := container.GetContainerPidInformation(descriptors) if err != nil { return err diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go index c56e35aef..371e88495 100644 --- a/cmd/podman/tree.go +++ b/cmd/podman/tree.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" - units "github.com/docker/go-units" + "github.com/containers/libpod/pkg/adapter" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,16 +41,6 @@ func init() { treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image") } -// infoImage keep information of Image along with all associated layers -type infoImage struct { - // id of image - id string - // tags of image - tags []string - // layers stores all layers of image. - layers []image.LayerInfo -} - func treeCmd(c *cliconfig.TreeValues) error { args := c.InputArgs if len(args) == 0 { @@ -60,46 +50,33 @@ func treeCmd(c *cliconfig.TreeValues) error { return errors.Errorf("you must provide at most 1 argument") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - - img, err := runtime.ImageRuntime().NewFromLocal(args[0]) + imageInfo, layerInfoMap, img, err := runtime.Tree(c) if err != nil { return err } + return printTree(imageInfo, layerInfoMap, img, c.WhatRequires) +} - // Fetch map of image-layers, which is used for printing output. - layerInfoMap, err := image.GetLayersMapWithImageInfo(runtime.ImageRuntime()) - if err != nil { - return errors.Wrapf(err, "error while retriving layers of image %q", img.InputName) - } - - // Create an imageInfo and fill the image and layer info - imageInfo := &infoImage{ - id: img.ID(), - tags: img.Names(), - } - +func printTree(imageInfo *image.InfoImage, layerInfoMap map[string]*image.LayerInfo, img *adapter.ContainerImage, whatRequires bool) error { size, err := img.Size(context.Background()) if err != nil { - return errors.Wrapf(err, "error while retriving image size") + return err } - fmt.Printf("Image ID: %s\n", imageInfo.id[:12]) - fmt.Printf("Tags:\t %s\n", imageInfo.tags) + + fmt.Printf("Image ID: %s\n", imageInfo.ID[:12]) + fmt.Printf("Tags:\t %s\n", imageInfo.Tags) fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4)) fmt.Printf(fmt.Sprintf("Image Layers\n")) - if !c.WhatRequires { + if !whatRequires { // fill imageInfo with layers associated with image. // the layers will be filled such that // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) - err := buildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()) - if err != nil { - return err - } // Build output from imageInfo into buffer printImageHierarchy(imageInfo) @@ -108,30 +85,8 @@ func treeCmd(c *cliconfig.TreeValues) error { // the layers will be filled such that // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) - err := printImageChildren(layerInfoMap, img.TopLayer(), "", true) - if err != nil { - return err - } - } - - return nil -} - -// Stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo -// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) -func buildImageHierarchyMap(imageInfo *infoImage, layerMap map[string]*image.LayerInfo, layerID string) error { - if layerID == "" { - return nil - } - ll, ok := layerMap[layerID] - if !ok { - return fmt.Errorf("lookup error: layerid %s not found", layerID) + return printImageChildren(layerInfoMap, img.TopLayer(), "", true) } - if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { - return err - } - - imageInfo.layers = append(imageInfo.layers, *ll) return nil } @@ -175,14 +130,14 @@ func printImageChildren(layerMap map[string]*image.LayerInfo, layerID string, pr } // prints the layers info of image -func printImageHierarchy(imageInfo *infoImage) { - for count, l := range imageInfo.layers { +func printImageHierarchy(imageInfo *image.InfoImage) { + for count, l := range imageInfo.Layers { var tags string intend := middleItem if len(l.RepoTags) > 0 { tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags) } - if count == len(imageInfo.layers)-1 { + if count == len(imageInfo.Layers)-1 { intend = lastItem } fmt.Printf("%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags) diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 2ff06a6f6..92fdcd20f 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -68,7 +68,8 @@ type Image ( virtualSize: int, containers: int, labels: [string]string, - isParent: bool + isParent: bool, + topLayer: string ) # ImageHistory describes the returned structure from ImageHistory. @@ -1161,8 +1162,15 @@ method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) # GetEvents returns known libpod events filtered by the options provided. method GetEvents(filter: []string, since: string, until: string) -> (events: Event) +# Diff returns a diff between libpod objects method Diff(name: string) -> (diffs: []DiffInfo) +# GetLayersMapWithImageInfo is for the development of Podman and should not be used. +method GetLayersMapWithImageInfo() -> (layerMap: string) + +# BuildImageHierarchyMap is for the development of Podman and should not be used. +method BuildImageHierarchyMap(name: string) -> (imageInfo: string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string, reason: string) diff --git a/docs/podman-volume-rm.1.md b/docs/podman-volume-rm.1.md index c23d7675c..8c3765235 100644 --- a/docs/podman-volume-rm.1.md +++ b/docs/podman-volume-rm.1.md @@ -21,7 +21,8 @@ Remove all volumes. **-f**, **--force**="" -Remove a volume by force, even if it is being used by a container +Remove a volume by force. +If it is being used by containers, the containers will be removed first. **--help** diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 92a7b1538..d8cfa2bda 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1358,56 +1358,6 @@ func (s *BoltState) AddVolume(volume *Volume) error { return err } -// RemoveVolCtrDep updates the container dependencies sub bucket of the given volume. -// It deletes it from the bucket when found. -// This is important when force removing a volume and we want to get rid of the dependencies. -func (s *BoltState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if ctrID == "" { - return nil - } - - if !s.valid { - return ErrDBBadConfig - } - - if !volume.valid { - return ErrVolumeRemoved - } - - volName := []byte(volume.Name()) - - db, err := s.getDBCon() - if err != nil { - return err - } - defer s.closeDBCon(db) - - err = db.Update(func(tx *bolt.Tx) error { - volBkt, err := getVolBucket(tx) - if err != nil { - return err - } - - volDB := volBkt.Bucket(volName) - if volDB == nil { - volume.valid = false - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volume.Name()) - } - - // Make a subbucket for the containers using the volume - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - depCtrID := []byte(ctrID) - if depExists := ctrDepsBkt.Get(depCtrID); depExists != nil { - if err := ctrDepsBkt.Delete(depCtrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctrID, volume.Name()) - } - } - - return nil - }) - return err -} - // RemoveVolume removes the given volume from the state func (s *BoltState) RemoveVolume(volume *Volume) error { if !s.valid { @@ -1433,6 +1383,11 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { return err } + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + // Check if the volume exists volDB := volBkt.Bucket(volName) if volDB == nil { @@ -1448,6 +1403,18 @@ func (s *BoltState) RemoveVolume(volume *Volume) error { if volCtrsBkt != nil { var deps []string err = volCtrsBkt.ForEach(func(id, value []byte) error { + // Alright, this is ugly. + // But we need it to work around the change in + // volume dependency handling, to make sure that + // older Podman versions don't cause DB + // corruption. + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBkt.Bucket(id) + if ctrExists == nil { + return nil + } + deps = append(deps, string(id)) return nil }) @@ -1629,6 +1596,11 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { return err } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + volDB := volBucket.Bucket([]byte(volume.Name())) if volDB == nil { volume.valid = false @@ -1642,6 +1614,13 @@ func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { // Iterate through and add dependencies err = dependsBkt.ForEach(func(id, value []byte) error { + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBucket.Bucket(id) + if ctrExists == nil { + return nil + } + depCtrs = append(depCtrs, string(id)) return nil diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index b6a0759b1..a6900a6d3 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -564,23 +564,17 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { } } - // Add container to volume dependencies bucket if container is using a named volume - if ctr.runtime.config.VolumePath == "" { - return nil - } - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volName) - } + // Add container to named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database when adding container %s", vol.Name, ctr.ID()) + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { - if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { - return errors.Wrapf(err, "error storing container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s to volume %s dependencies", ctr.ID(), vol.Name) } } } @@ -745,22 +739,19 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error } } - // Remove container from volume dependencies bucket if container is using a named volume - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - - volDB := volBkt.Bucket([]byte(volName)) - if volDB == nil { - // Let's assume the volume was already deleted and continue to remove the container - continue - } + // Remove container from named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + // Let's assume the volume was already deleted and + // continue to remove the container + continue + } - ctrDepsBkt := volDB.Bucket(volDependenciesBkt) - if depExists := ctrDepsBkt.Get(ctrID); depExists != nil { - if err := ctrDepsBkt.Delete(ctrID); err != nil { - return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctr.ID(), volName) - } + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s dependency on volume %s", ctr.ID(), vol.Name) } } } diff --git a/libpod/container.go b/libpod/container.go index 739406e42..6d5e063ab 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -234,6 +234,8 @@ type ContainerConfig struct { // These include the SHM mount. // These must be unmounted before the container's rootfs is unmounted. Mounts []string `json:"mounts,omitempty"` + // NamedVolumes lists the named volumes to mount into the container. + NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // Security Config @@ -354,9 +356,6 @@ type ContainerConfig struct { // ExitCommand is the container's exit command. // This Command will be executed when the container exits ExitCommand []string `json:"exitCommand,omitempty"` - // LocalVolumes are the built-in volumes we get from the --volumes-from flag - // It picks up the built-in volumes of the container used by --volumes-from - LocalVolumes []spec.Mount // IsInfra is a bool indicating whether this container is an infra container used for // sharing kernel namespaces in a pod IsInfra bool `json:"pause"` @@ -368,6 +367,18 @@ type ContainerConfig struct { HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` } +// ContainerNamedVolume is a named volume that will be mounted into the +// container. Each named volume is a libpod Volume present in the state. +type ContainerNamedVolume struct { + // Name is the name of the volume to mount in. + // Must resolve to a valid volume present in this Podman. + Name string `json:"volumeName"` + // Dest is the mount's destination + Dest string `json:"dest"` + // Options are fstab style mount options + Options []string `json:"options,omitempty"` +} + // ContainerStatus returns a string representation for users // of a container state func (t ContainerStatus) String() string { @@ -488,6 +499,22 @@ func (c *Container) StaticDir() string { return c.config.StaticDir } +// NamedVolumes returns the container's named volumes. +// The name of each is guaranteed to point to a valid libpod Volume present in +// the state. +func (c *Container) NamedVolumes() []*ContainerNamedVolume { + volumes := []*ContainerNamedVolume{} + for _, vol := range c.config.NamedVolumes { + newVol := new(ContainerNamedVolume) + newVol.Name = vol.Name + newVol.Dest = vol.Dest + newVol.Options = vol.Options + volumes = append(volumes, newVol) + } + + return volumes +} + // Privileged returns whether the container is privileged func (c *Container) Privileged() bool { return c.config.Privileged diff --git a/libpod/container_internal.go b/libpod/container_internal.go index daa32007a..22df36c11 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1403,22 +1403,6 @@ func getExcludedCGroups() (excludes []string) { return } -// namedVolumes returns named volumes for the container -func (c *Container) namedVolumes() ([]string, error) { - var volumes []string - for _, vol := range c.config.Spec.Mounts { - if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) { - volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/") - split := strings.Split(volume, "/") - volume = split[0] - if _, err := c.runtime.state.Volume(volume); err == nil { - volumes = append(volumes, volume) - } - } - } - return volumes, nil -} - // this should be from chrootarchive. func (c *Container) copyWithTarFromImage(src, dest string) error { mountpoint, err := c.mount() diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 504d6c135..4d6bf61a3 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -195,6 +195,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { 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 i := range g.Config.Mounts { @@ -218,6 +219,23 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.SetProcessSelinuxLabel(c.ProcessLabel()) g.SetLinuxMountLabel(c.MountLabel()) + + // Add named volumes + for _, namedVol := range c.config.NamedVolumes { + volume, err := c.runtime.GetVolume(namedVol.Name) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID()) + } + mountPoint := volume.MountPoint() + volMount := spec.Mount{ + Type: "bind", + Source: mountPoint, + Destination: namedVol.Dest, + Options: namedVol.Options, + } + g.AddMount(volMount) + } + // Add bind mounts to container for dstPath, srcPath := range c.state.BindMounts { newMount := spec.Mount{ diff --git a/libpod/image/image.go b/libpod/image/image.go index 4862bf1d6..cc056b816 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -68,6 +68,16 @@ type Runtime struct { EventsLogFilePath string } +// InfoImage keep information of Image along with all associated layers +type InfoImage struct { + // ID of image + ID string + // Tags of image + Tags []string + // Layers stores all layers of image. + Layers []LayerInfo +} + // ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store var ErrRepoTagNotFound = errors.New("unable to match user input to any specific repotag") @@ -1277,3 +1287,21 @@ func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, er } return layerInfoMap, nil } + +// BuildImageHierarchyMap stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo +// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End) +func BuildImageHierarchyMap(imageInfo *InfoImage, layerMap map[string]*LayerInfo, layerID string) error { + if layerID == "" { + return nil + } + ll, ok := layerMap[layerID] + if !ok { + return fmt.Errorf("lookup error: layerid %s not found", layerID) + } + if err := BuildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { + return err + } + + imageInfo.Layers = append(imageInfo.Layers, *ll) + return nil +} diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index ab4fc8ba7..2669206df 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -249,11 +249,8 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { } // Add container to volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.addCtrToVolDependsMap(ctr.ID(), volName) - } + for _, vol := range ctr.config.NamedVolumes { + s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -306,12 +303,9 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { s.removeCtrFromDependsMap(ctr.ID(), depCtr) } - // Remove container from volume dependencies - for _, vol := range ctr.config.Spec.Mounts { - if strings.Contains(vol.Source, ctr.runtime.config.VolumePath) { - volName := strings.Split(vol.Source[len(ctr.runtime.config.VolumePath)+1:], "/")[0] - s.removeCtrFromVolDependsMap(ctr.ID(), volName) - } + // Remove this container from volume dependencies + for _, vol := range ctr.config.NamedVolumes { + s.removeCtrFromVolDependsMap(ctr.ID(), vol.Name) } return nil @@ -492,22 +486,6 @@ func (s *InMemoryState) RemoveVolume(volume *Volume) error { return nil } -// RemoveVolCtrDep updates the container dependencies of the volume -func (s *InMemoryState) RemoveVolCtrDep(volume *Volume, ctrID string) error { - if !volume.valid { - return errors.Wrapf(ErrVolumeRemoved, "volume with name %s is not valid", volume.Name()) - } - - if _, ok := s.volumes[volume.Name()]; !ok { - return errors.Wrapf(ErrNoSuchVolume, "volume with name %s doesn't exists in state", volume.Name()) - } - - // Remove container that is using this volume - s.removeCtrFromVolDependsMap(ctrID, volume.Name()) - - return nil -} - // VolumeInUse checks if the given volume is being used by at least one container func (s *InMemoryState) VolumeInUse(volume *Volume) ([]string, error) { if !volume.valid { diff --git a/libpod/options.go b/libpod/options.go index 24f126e66..9326e54e4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -13,7 +13,6 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -1111,24 +1110,6 @@ func WithUserVolumes(volumes []string) CtrCreateOption { } } -// WithLocalVolumes sets the built-in volumes of the container retrieved -// from a container passed in to the --volumes-from flag. -// This stores the built-in volume information in the Config so we can -// add them when creating the container. -func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return ErrCtrFinalized - } - - if volumes != nil { - ctr.config.LocalVolumes = append(ctr.config.LocalVolumes, volumes...) - } - - return nil - } -} - // WithEntrypoint sets the entrypoint of the container. // This is not used to change the container's spec, but will instead be used // during commit to populate the entrypoint of the new image. @@ -1255,6 +1236,35 @@ func withIsInfra() CtrCreateOption { } } +// WithNamedVolumes adds the given named volumes to the container. +func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + destinations := make(map[string]bool) + + for _, vol := range volumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + + if _, ok := destinations[vol.Dest]; ok { + return errors.Wrapf(ErrInvalidArg, "two volumes found with destination %s", vol.Dest) + } + destinations[vol.Dest] = true + + ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{ + Name: vol.Name, + Dest: vol.Dest, + Options: vol.Options, + }) + } + + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. @@ -1274,68 +1284,72 @@ func WithVolumeName(name string) VolumeCreateOption { } } -// WithVolumeUID sets the uid of the owner. -func WithVolumeUID(uid int) VolumeCreateOption { +// WithVolumeLabels sets the labels of the volume. +func WithVolumeLabels(labels map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.UID = uid + + volume.config.Labels = make(map[string]string) + for key, value := range labels { + volume.config.Labels[key] = value + } + return nil } } -// WithVolumeGID sets the gid of the owner. -func WithVolumeGID(gid int) VolumeCreateOption { +// WithVolumeDriver sets the driver of the volume. +func WithVolumeDriver(driver string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.GID = gid + + volume.config.Driver = driver + return nil } } -// WithVolumeLabels sets the labels of the volume. -func WithVolumeLabels(labels map[string]string) VolumeCreateOption { +// WithVolumeOptions sets the options of the volume. +func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Labels = make(map[string]string) - for key, value := range labels { - volume.config.Labels[key] = value + volume.config.Options = make(map[string]string) + for key, value := range options { + volume.config.Options[key] = value } return nil } } -// WithVolumeDriver sets the driver of the volume. -func WithVolumeDriver(driver string) VolumeCreateOption { +// WithVolumeUID sets the UID that the volume will be created as. +func WithVolumeUID(uid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Driver = driver + volume.config.UID = uid return nil } } -// WithVolumeOptions sets the options of the volume. -func WithVolumeOptions(options map[string]string) VolumeCreateOption { +// WithVolumeGID sets the GID that the volume will be created as. +func WithVolumeGID(gid int) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { return ErrVolumeFinalized } - volume.config.Options = make(map[string]string) - for key, value := range options { - volume.config.Options[key] = value - } + volume.config.GID = gid return nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index 6e54de558..4dd2707e8 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "sync" "syscall" @@ -742,7 +741,7 @@ func makeRuntime(runtime *Runtime) (err error) { // Set up containers/storage var store storage.Store - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { logrus.Debug("Not configuring container store") } else { store, err = storage.GetStore(runtime.config.StorageConfig) @@ -926,16 +925,8 @@ func makeRuntime(runtime *Runtime) (err error) { // If we need to refresh the state, do it now - things are guaranteed to // be set up by now. if doRefresh { - if os.Geteuid() != 0 { - aliveLock.Unlock() - locked = false - if err2 := runtime.refreshRootless(); err2 != nil { - return err2 - } - } else { - if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { - return err2 - } + if err2 := runtime.refresh(runtimeAliveFile); err2 != nil { + return err2 } } @@ -1009,21 +1000,6 @@ func (r *Runtime) Shutdown(force bool) error { return lastError } -// Reconfigures the runtime after a reboot for a rootless process -func (r *Runtime) refreshRootless() error { - // Take advantage of a command that requires a new userns - // so that we are running as the root user and able to use refresh() - cmd := exec.Command(os.Args[0], "info") - - if output, err := cmd.CombinedOutput(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return errors.Wrapf(err, "Error waiting for info while refreshing state: %s", os.Args[0]) - } - return errors.Wrapf(err, "Error running %s info while refreshing state: %s", os.Args[0], output) - } - return nil -} - // Reconfigures the runtime after a reboot // Refreshes the state, recreating temporary files // Does not check validity as the runtime is not valid until after this has run diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 506aee477..800b42851 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,11 +2,9 @@ package libpod import ( "context" - "io/ioutil" "os" "path" "path/filepath" - "strconv" "strings" "time" @@ -101,9 +99,6 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.state.State = ContainerStateConfigured ctr.runtime = r - ctr.valid = true - ctr.state.State = ContainerStateConfigured - var pod *Pod if ctr.config.Pod != "" { // Get the pod from state @@ -175,24 +170,29 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") } - // Go through the volume mounts and check for named volumes - // If the named volme already exists continue, otherwise create - // the storage for the named volume. - for i, vol := range ctr.config.Spec.Mounts { - if vol.Source[0] != '/' && isNamedVolume(vol.Source) { - volInfo, err := r.state.Volume(vol.Source) - if err != nil { - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source), withSetCtrSpecific(), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) - if err != nil { - return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) - } - ctr.config.Spec.Mounts[i].Source = newVol.MountPoint() - if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "failed to copy content into new volume mount %q", vol.Source) - } - continue - } - ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint() + // Go through named volumes and add them. + // If they don't exist they will be created using basic options. + for _, vol := range ctr.config.NamedVolumes { + // Check if it exists already + _, err := r.state.Volume(vol.Name) + if err == nil { + // The volume exists, we're good + continue + } else if errors.Cause(err) != ErrNoSuchVolume { + return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) + } + + logrus.Debugf("Creating new volume %s for container", vol.Name) + + // The volume does not exist, so we need to create it. + newVol, err := r.newVolume(ctx, WithVolumeName(vol.Name), withSetCtrSpecific(), + WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + if err != nil { + return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) + } + + if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name) } } @@ -346,13 +346,6 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) } - var volumes []string - if removeVolume { - volumes, err = c.namedVolumes() - if err != nil { - logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err) - } - } var cleanupErr error // Remove the container from the state if c.config.Pod != "" { @@ -417,8 +410,12 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } - for _, v := range volumes { - if volume, err := runtime.state.Volume(v); err == nil { + if !removeVolume { + return cleanupErr + } + + for _, v := range c.config.NamedVolumes { + if volume, err := runtime.state.Volume(v.Name); err == nil { if !volume.IsCtrSpecific() { continue } @@ -550,51 +547,12 @@ func (r *Runtime) GetLatestContainer() (*Container, error) { return ctrs[lastCreatedIndex], nil } -// Check if volName is a named volume and not one of the default mounts we add to containers -func isNamedVolume(volName string) bool { - if volName != "proc" && volName != "tmpfs" && volName != "devpts" && volName != "shm" && volName != "mqueue" && volName != "sysfs" && volName != "cgroup" { - return true - } - return false -} - // Export is the libpod portion of exporting a container to a tar file func (r *Runtime) Export(name string, path string) error { ctr, err := r.LookupContainer(name) if err != nil { return err } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == ContainerStateRunning || state == ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } return ctr.Export(path) } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index db5c29242..40040fc52 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -98,12 +98,26 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error if !force { return errors.Wrapf(ErrVolumeBeingUsed, "volume %s is being used by the following container(s): %s", v.Name(), depsStr) } - // If using force, log the warning that the volume is being used by at least one container - logrus.Warnf("volume %s is being used by the following container(s): %s", v.Name(), depsStr) - // Remove the container dependencies so we can go ahead and delete the volume + + // We need to remove all containers using the volume for _, dep := range deps { - if err := r.state.RemoveVolCtrDep(v, dep); err != nil { - return errors.Wrapf(err, "unable to remove container dependency %q from volume %q while trying to delete volume by force", dep, v.Name()) + ctr, err := r.state.Container(dep) + if err != nil { + // If the container's removed, no point in + // erroring. + if errors.Cause(err) == ErrNoSuchCtr || errors.Cause(err) == ErrCtrRemoved { + continue + } + + return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name()) + } + + // TODO: do we want to set force here when removing + // containers? + // I'm inclined to say no, in case someone accidentally + // wipes a container they're using... + if err := r.removeContainer(ctx, ctr, false, false); err != nil { + return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) } } } diff --git a/libpod/state.go b/libpod/state.go index 4296fc3cd..d0ad1a1f8 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -192,10 +192,6 @@ type State interface { // AddVolume adds the specified volume to state. The volume's name // must be unique within the list of existing volumes AddVolume(volume *Volume) error - // RemoveVolCtrDep updates the list of container dependencies that the - // volume has. It either deletes the dependent container ID from - // the sub-bucket - RemoveVolCtrDep(volume *Volume, ctrID string) error // RemoveVolume removes the specified volume. // Only volumes that have no container dependencies can be removed RemoveVolume(volume *Volume) error diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go new file mode 100644 index 000000000..c8ea1cdea --- /dev/null +++ b/pkg/adapter/images.go @@ -0,0 +1,34 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" +) + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + // Fetch map of image-layers, which is used for printing output. + layerInfoMap, err := image.GetLayersMapWithImageInfo(r.Runtime.ImageRuntime()) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error while retrieving layers of image %q", img.InputName) + } + + // Create an imageInfo and fill the image and layer info + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + + if err := image.BuildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()); err != nil { + return nil, nil, nil, err + } + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..722058d4a 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,11 @@ import ( "context" "encoding/json" + "github.com/containers/libpod/cmd/podman/cliconfig" iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" ) // Inspect returns returns an ImageData struct from over a varlink connection @@ -22,3 +25,32 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error } return &data, nil } + +// Tree ... +func (r *LocalRuntime) Tree(c *cliconfig.TreeValues) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) { + layerInfoMap := make(map[string]*image.LayerInfo) + imageInfo := &image.InfoImage{} + + img, err := r.NewImageFromLocal(c.InputArgs[0]) + if err != nil { + return nil, nil, nil, err + } + + reply, err := iopodman.GetLayersMapWithImageInfo().Call(r.Conn) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to obtain image layers") + } + if err := json.Unmarshal([]byte(reply), &layerInfoMap); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers") + } + + reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, c.InputArgs[0]) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to get build image map") + } + if err := json.Unmarshal([]byte(reply), imageInfo); err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to unmarshal build image map") + } + + return imageInfo, layerInfoMap, img, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index dd51c7233..182a04044 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -8,7 +8,6 @@ import ( "io" "io/ioutil" "os" - "strconv" "text/template" "github.com/containers/buildah" @@ -124,38 +123,6 @@ func (r *LocalRuntime) Export(name string, path string) error { if err != nil { return errors.Wrapf(err, "error looking up container %q", name) } - if os.Geteuid() != 0 { - state, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) - } - if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { - data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) - if err != nil { - return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return errors.Wrapf(err, "cannot parse PID %q", data) - } - became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } else { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - } - return ctr.Export(path) } @@ -343,46 +310,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return r.Runtime.HealthCheck(c.InputArgs[0]) } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - if os.Geteuid() == 0 { - return false, 0, nil - } - opts := rootless.Opts{ - Argument: pod.ID(), - } - - inspect, err := pod.Inspect() - if err != nil { - return false, 0, err - } - for _, ctr := range inspect.Containers { - prevCtr, err := r.LookupContainer(ctr.ID) - if err != nil { - return false, -1, err - } - s, err := prevCtr.State() - if err != nil { - return false, -1, err - } - if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { - continue - } - data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) - } - conmonPid, err := strconv.Atoi(string(data)) - if err != nil { - return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) - } - return rootless.JoinDirectUserAndMountNSWithOpts(uint(conmonPid), &opts) - } - - return rootless.BecomeRootInUserNSWithOpts(&opts) -} - // Events is a wrapper to libpod to obtain libpod/podman events func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var ( diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index c3a4f322d..807a9ad8f 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -82,6 +82,7 @@ type remoteImage struct { Digest digest.Digest isParent bool Runtime *LocalRuntime + TopLayer string } // Container ... @@ -147,6 +148,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu Names: i.RepoTags, isParent: i.IsParent, Runtime: runtime, + TopLayer: i.TopLayer, } return &ContainerImage{ri}, nil } @@ -280,6 +282,11 @@ func (ci *ContainerImage) Dangling() bool { return len(ci.Names()) == 0 } +// TopLayer returns an images top layer as a string +func (ci *ContainerImage) TopLayer() string { + return ci.remoteImage.TopLayer +} + // TagImage ... func (ci *ContainerImage) TagImage(tag string) error { _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag) @@ -755,13 +762,6 @@ func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.Healt return -1, libpod.ErrNotImplemented } -// JoinOrCreateRootlessPod joins the specified pod if it is running or it creates a new user namespace -// if the pod is stopped -func (r *LocalRuntime) JoinOrCreateRootlessPod(pod *Pod) (bool, int, error) { - // Nothing to do in the remote case - return true, 0, nil -} - // Events monitors libpod/podman events over a varlink connection func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { var more uint64 diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 008cca7ee..fe2591a0c 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -19,9 +19,18 @@ const ( // HostName is the container host name annotation HostName = "io.kubernetes.cri-o.HostName" + // CgroupParent is the sandbox cgroup parent + CgroupParent = "io.kubernetes.cri-o.CgroupParent" + // IP is the container ipv4 or ipv6 address IP = "io.kubernetes.cri-o.IP" + // NamespaceOptions store the options for namespaces + NamespaceOptions = "io.kubernetes.cri-o.NamespaceOptions" + + // SeccompProfilePath is the node seccomp profile path + SeccompProfilePath = "io.kubernetes.cri-o.SeccompProfilePath" + // Image is the container image ID annotation Image = "io.kubernetes.cri-o.Image" @@ -34,6 +43,9 @@ const ( // KubeName is the kubernetes name annotation KubeName = "io.kubernetes.cri-o.KubeName" + // PortMappings holds the port mappings for the sandbox + PortMappings = "io.kubernetes.cri-o.PortMappings" + // Labels are the kubernetes labels annotation Labels = "io.kubernetes.cri-o.Labels" @@ -46,6 +58,9 @@ const ( // Name is the pod name annotation Name = "io.kubernetes.cri-o.Name" + // Namespace is the pod namespace annotation + Namespace = "io.kubernetes.cri-o.Namespace" + // PrivilegedRuntime is the annotation for the privileged runtime path PrivilegedRuntime = "io.kubernetes.cri-o.PrivilegedRuntime" @@ -67,8 +82,8 @@ const ( // MountPoint is the mount point of the container rootfs MountPoint = "io.kubernetes.cri-o.MountPoint" - // TrustedSandbox is the annotation for trusted sandboxes - TrustedSandbox = "io.kubernetes.cri-o.TrustedSandbox" + // RuntimeHandler is the annotation for runtime handler + RuntimeHandler = "io.kubernetes.cri-o.RuntimeHandler" // TTY is the terminal path annotation TTY = "io.kubernetes.cri-o.TTY" @@ -79,8 +94,14 @@ const ( // StdinOnce is the stdin_once annotation StdinOnce = "io.kubernetes.cri-o.StdinOnce" - // Volumes is the volumes annotation + // Volumes is the volumes annotatoin Volumes = "io.kubernetes.cri-o.Volumes" + + // HostNetwork indicates whether the host network namespace is used or not + HostNetwork = "io.kubernetes.cri-o.HostNetwork" + + // CNIResult is the JSON string representation of the Result from CNI + CNIResult = "io.kubernetes.cri-o.CNIResult" ) // ContainerType values diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go deleted file mode 100644 index a531e43ce..000000000 --- a/pkg/rootless/rootless.go +++ /dev/null @@ -1,9 +0,0 @@ -package rootless - -// Opts allows to customize how re-execing to a rootless process is done -type Opts struct { - // Argument overrides the arguments on the command line - // for the re-execed process. The process in the namespace - // must use rootless.Argument() to read its value. - Argument string -} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 2e2c3acac..9cb79ed4d 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -13,10 +13,36 @@ #include <sys/wait.h> #include <string.h> #include <stdbool.h> +#include <sys/types.h> +#include <sys/prctl.h> +#include <dirent.h> static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; +static int n_files; + +static void __attribute__((constructor)) init() +{ + DIR *d; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir ("/proc/self/fd"); + if (d) + { + struct dirent *ent; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd = atoi (ent->d_name); + if (fd > n_files && fd != dirfd (d)) + n_files = fd; + } + closedir (d); + } +} + + static int syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid) { @@ -133,12 +159,25 @@ reexec_userns_join (int userns, int mountns) pid = fork (); if (pid < 0) fprintf (stderr, "cannot fork: %s\n", strerror (errno)); + if (pid) - return pid; + { + /* We passed down these fds, close them. */ + int f; + for (f = 3; f < n_files; f++) + close (f); + return pid; + } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) + { + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno)); + _exit (EXIT_FAILURE); + } + if (setns (userns, 0) < 0) { fprintf (stderr, "cannot setns: %s\n", strerror (errno)); diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 0be0e08bf..1d1b1713d 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -46,25 +46,6 @@ func IsRootless() bool { return isRootless } -var ( - skipStorageSetup = false -) - -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(v bool) { - skipStorageSetup = v -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return skipStorageSetup -} - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return os.Getenv("_CONTAINERS_ROOTLESS_ARG") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") @@ -104,51 +85,86 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) return nil } -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil +func readUserNs(path string) (string, error) { + b := make([]byte, 256) + _, err := syscall.Readlink(path, b) + if err != nil { + return "", err } + return string(b), nil +} - userNS, err := getUserNSForPid(pid) +func readUserNsFd(fd uintptr) (string, error) { + return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) +} + +func getParentUserNs(fd uintptr) (uintptr, error) { + const nsGetParent = 0xb702 + ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + if errno != 0 { + return 0, errno + } + return (uintptr)(unsafe.Pointer(ret)), nil +} + +// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process +// Each container creates a new user namespace where the runtime runs. The current process in the container +// might have created new user namespaces that are child of the initial namespace we created. +// This function finds the initial namespace created for the container that is a child of the current namespace. +// +// current ns +// / \ +// TARGET -> a [other containers] +// / +// b +// / +// NS READ USING THE PID -> c +func getUserNSFirstChild(fd uintptr) (*os.File, error) { + currentNS, err := readUserNs("/proc/self/ns/user") if err != nil { - return false, -1, err + return nil, err } - defer userNS.Close() - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") + ns, err := readUserNsFd(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") } - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() - } + if ns == currentNS { + return nil, errors.New("process running in the same user namespace") } - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } + for { + nextFd, err := getParentUserNs(fd) + if err != nil { + return nil, errors.Wrapf(err, "cannot get parent user namespace") + } - return true, int(ret), nil -} + ns, err = readUserNsFd(nextFd) + if err != nil { + return nil, errors.Wrapf(err, "cannot read user namespace") + } -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount -// namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts -// with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return JoinDirectUserAndMountNSWithOpts(pid, nil) + if ns == currentNS { + syscall.Close(int(nextFd)) + + // Drop O_CLOEXEC for the fd. + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + if errno != 0 { + syscall.Close(int(fd)) + return nil, errno + } + + return os.NewFile(fd, "userns child"), nil + } + syscall.Close(int(fd)) + fd = nextFd + } } -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func JoinUserAndMountNS(pid uint) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -165,39 +181,11 @@ func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { } defer userNS.Close() - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), C.int(mountNS.Fd())) - if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") - } - - ret := C.reexec_in_user_namespace_wait(pidC) - if ret < 0 { - return false, -1, errors.New("error waiting for the re-exec process") - } - - return true, int(ret), nil -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { - return false, -1, nil - } - - userNS, err := getUserNSForPath(path) + fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { return false, -1, err } - defer userNS.Close() - - pidC := C.reexec_userns_join(C.int(userNS.Fd()), -1) + pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd())) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } @@ -213,16 +201,8 @@ func JoinNSPath(path string) (bool, int, error) { // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed // into a new user namespace and the return code from the re-executed podman process. // If podman was re-executed the caller needs to propagate the error code returned by the child -// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. -func BecomeRootInUserNS() (bool, int, error) { - return BecomeRootInUserNSWithOpts(nil) -} - -// BecomeRootInUserNSWithOpts re-exec podman in a new userNS. It returns whether podman was -// re-execute into a new user namespace and the return code from the re-executed podman process. -// If podman was re-executed the caller needs to propagate the error code returned by the child // process. -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { +func BecomeRootInUserNS() (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -241,12 +221,6 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { defer w.Close() defer w.Write([]byte("0")) - if opts != nil && opts.Argument != "" { - if err := os.Setenv("_CONTAINERS_ROOTLESS_ARG", opts.Argument); err != nil { - return false, -1, err - } - } - pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) if pid < 0 { @@ -328,112 +302,3 @@ func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { return true, int(ret), nil } - -func readUserNs(path string) (string, error) { - b := make([]byte, 256) - _, err := syscall.Readlink(path, b) - if err != nil { - return "", err - } - return string(b), nil -} - -func readUserNsFd(fd uintptr) (string, error) { - return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) -} - -func getOwner(fd uintptr) (uintptr, error) { - const nsGetUserns = 0xb701 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetUserns), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getParentUserNs(fd uintptr) (uintptr, error) { - const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -func getUserNSForPath(path string) (*os.File, error) { - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - defer u.Close() - fd, err := getOwner(u.Fd()) - if err != nil { - return nil, err - } - - return getUserNSFirstChild(fd) -} - -func getUserNSForPid(pid uint) (*os.File, error) { - path := fmt.Sprintf("/proc/%d/ns/user", pid) - u, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - - return getUserNSFirstChild(u.Fd()) -} - -// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process -// Each container creates a new user namespace where the runtime runs. The current process in the container -// might have created new user namespaces that are child of the initial namespace we created. -// This function finds the initial namespace created for the container that is a child of the current namespace. -// -// current ns -// / \ -// TARGET -> a [other containers] -// / -// b -// / -// NS READ USING THE PID -> c -func getUserNSFirstChild(fd uintptr) (*os.File, error) { - currentNS, err := readUserNs("/proc/self/ns/user") - if err != nil { - return nil, err - } - - ns, err := readUserNsFd(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - if ns == currentNS { - return nil, errors.New("process running in the same user namespace") - } - - for { - nextFd, err := getParentUserNs(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot get parent user namespace") - } - - ns, err = readUserNsFd(nextFd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - - if ns == currentNS { - syscall.Close(int(nextFd)) - - // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) - if errno != 0 { - syscall.Close(int(fd)) - return nil, errno - } - - return os.NewFile(fd, "userns child"), nil - } - syscall.Close(int(fd)) - fd = nextFd - } -} diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index e01d7855c..47b5dd7cc 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -19,54 +19,15 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } -// BecomeRootInUserNS is a stub function that always returns false and an -// error on unsupported OS's -func BecomeRootInUserNSWithOpts(opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { return -1 } -// SetSkipStorageSetup tells the runtime to not setup containers/storage -func SetSkipStorageSetup(bool) { -} - -// SkipStorageSetup tells if we should skip the containers/storage setup -func SkipStorageSetup() bool { - return false -} - -// JoinNS re-exec podman in a new userNS and join the user namespace of the specified -// PID. -func JoinNS(pid uint, preserveFDs int) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinNSPath re-exec podman in a new userNS and join the owner user namespace of the -// specified path. -func JoinNSPath(path string) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNSWithOpts re-exec podman in a new userNS and join the user and -// mount namespace of the specified PID without looking up its parent. Useful to join -// directly the conmon process. -func JoinDirectUserAndMountNSWithOpts(pid uint, opts *Opts) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os") -} - -// JoinDirectUserAndMountNS re-exec podman in a new userNS and join the user and mount +// JoinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// the conmon process. It is a convenience function for JoinUserAndMountNSWithOpts // with a default configuration. -func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { +func JoinUserAndMountNS(pid uint) (bool, int, error) { return false, -1, errors.New("this function is not supported on this os") } - -// Argument returns the argument that was set for the rootless session. -func Argument() string { - return "" -} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 0a12e3dca..e71d9d3db 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -1,7 +1,6 @@ package createconfig import ( - "encoding/json" "fmt" "net" "os" @@ -12,7 +11,6 @@ import ( "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" "github.com/cri-o/ocicni/pkg/ocicni" @@ -24,18 +22,16 @@ import ( "golang.org/x/sys/unix" ) -type mountType string - // Type constants const ( bps = iota iops // TypeBind is the type for mounting host dir - TypeBind mountType = "bind" + TypeBind = "bind" // TypeVolume is the type for remote storage volumes - // TypeVolume mountType = "volume" // re-enable upon use + // TypeVolume = "volume" // re-enable upon use // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs mountType = "tmpfs" + TypeTmpfs = "tmpfs" ) // CreateResourceConfig represents resource elements in CreateConfig @@ -131,15 +127,15 @@ type CreateConfig struct { Mounts []spec.Mount //mounts Volumes []string //volume VolumesFrom []string - WorkDir string //workdir - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts + NamedVolumes []*libpod.ContainerNamedVolume // Filled in by CreateConfigToOCISpec + WorkDir string //workdir + LabelOpts []string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts SecurityOpts []string Rootfs string - LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag - Syslog bool // Whether to enable syslog on exit commands + Syslog bool // Whether to enable syslog on exit commands } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -173,9 +169,9 @@ func (c *CreateConfig) AddContainerInitBinary(path string) error { c.Command = append([]string{"/dev/init", "--"}, c.Command...) c.Mounts = append(c.Mounts, spec.Mount{ Destination: "/dev/init", - Type: "bind", + Type: TypeBind, Source: path, - Options: []string{"bind", "ro"}, + Options: []string{TypeBind, "ro"}, }) return nil } @@ -218,9 +214,9 @@ func (c *CreateConfig) initFSMounts() []spec.Mount { return mounts } -//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs +// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { - m := c.LocalVolumes + m := []spec.Mount{} for _, i := range c.Volumes { var options []string spliti := strings.Split(i, ":") @@ -256,9 +252,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e mount.Source = "tmpfs" mount.Options = append(mount.Options, "tmpcopyup") } else { + // TODO: Move support for this and tmpfs into libpod + // Should tmpfs also be handled as named volumes? Wouldn't be hard // This will cause a new local Volume to be created on your system mount.Source = stringid.GenerateNonCryptoID() - mount.Options = append(mount.Options, "bind") + mount.Options = append(mount.Options, TypeBind) } m = append(m, mount) } @@ -269,13 +267,12 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e // GetVolumesFrom reads the create-config artifact of the container to get volumes from // and adds it to c.Volumes of the current container. func (c *CreateConfig) GetVolumesFrom() error { - var options string - - if rootless.SkipStorageSetup() { + if os.Geteuid() != 0 { return nil } for _, vol := range c.VolumesFrom { + options := "" splitVol := strings.SplitN(vol, ":", 2) if len(splitVol) == 2 { options = splitVol[1] @@ -284,41 +281,60 @@ func (c *CreateConfig) GetVolumesFrom() error { if err != nil { return errors.Wrapf(err, "error looking up container %q", splitVol[0]) } - inspect, err := ctr.Inspect(false) - if err != nil { - return errors.Wrapf(err, "error inspecting %q", splitVol[0]) - } - var createArtifact CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err != nil { - return errors.Wrapf(err, "error getting create-config artifact for %q", splitVol[0]) + + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return errors.Errorf("error retrieving container %s spec", ctr.ID()) } - for key := range createArtifact.BuiltinImgVolumes { - for _, m := range inspect.Mounts { - if m.Destination == key { - c.LocalVolumes = append(c.LocalVolumes, m) - break + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true + localOptions := options + if localOptions == "" { + localOptions = strings.Join(mnt.Options, ",") } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions)) } } - for _, i := range createArtifact.Volumes { - // Volumes format is host-dir:ctr-dir[:options], so get the host and ctr dir - // and add on the options given by the user to the flag. - spliti := strings.SplitN(i, ":", 3) - // Throw error if mounting volume from container with Z option (private label) - // Override this by adding 'z' to options. - if len(spliti) > 2 && strings.Contains(spliti[2], "Z") && !strings.Contains(options, "z") { - return errors.Errorf("volume mounted with private option 'Z' in %q. Use option 'z' to mount in current container", ctr.ID()) + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true } - if options == "" { - // Mount the volumes with the default options - c.Volumes = append(c.Volumes, createArtifact.Volumes...) - } else { - c.Volumes = append(c.Volumes, spliti[0]+":"+spliti[1]+":"+options) + localOptions := options + if localOptions == "" { + localOptions = strings.Join(namedVol.Options, ",") + } + c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions)) + } + + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) } } } @@ -418,14 +434,20 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l // others, if they are included volumes := make([]string, 0, len(c.Volumes)) for _, vol := range c.Volumes { - volumes = append(volumes, strings.SplitN(vol, ":", 2)[0]) + // We always want the volume destination + splitVol := strings.SplitN(vol, ":", 3) + if len(splitVol) > 1 { + volumes = append(volumes, splitVol[1]) + } else { + volumes = append(volumes, splitVol[0]) + } } options = append(options, libpod.WithUserVolumes(volumes)) } - if len(c.LocalVolumes) != 0 { - options = append(options, libpod.WithLocalVolumes(c.LocalVolumes)) + if len(c.NamedVolumes) != 0 { + options = append(options, libpod.WithNamedVolumes(c.NamedVolumes)) } if len(c.Command) != 0 { @@ -539,7 +561,7 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithPrivileged(c.Privileged)) - useImageVolumes := c.ImageVolumeType == "bind" + useImageVolumes := c.ImageVolumeType == TypeBind // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes)) options = append(options, libpod.WithSecLabels(c.LabelOpts)) diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a61741f73..9b6bd089e 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" pmount "github.com/containers/storage/pkg/mount" @@ -48,6 +49,33 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M return configMount } +// Split named volumes from normal volumes +func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) { + newMounts := make([]spec.Mount, 0) + namedVolumes := make([]*libpod.ContainerNamedVolume, 0) + for _, mount := range mounts { + // If it's not a named volume, append unconditionally + if mount.Type != TypeBind { + newMounts = append(newMounts, mount) + continue + } + // Volumes that are not named volumes must be an absolute or + // relative path. + // Volume names may not begin with a non-alphanumeric character + // so the HasPrefix() check is safe here. + if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") { + newMounts = append(newMounts, mount) + } else { + namedVolume := new(libpod.ContainerNamedVolume) + namedVolume.Name = mount.Source + namedVolume.Dest = mount.Destination + namedVolume.Options = mount.Options + namedVolumes = append(namedVolumes, namedVolume) + } + } + return newMounts, namedVolumes +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -99,7 +127,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } sysMnt := spec.Mount{ Destination: "/sys", - Type: "bind", + Type: TypeBind, Source: "/sys", Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } @@ -126,7 +154,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ Destination: "/dev/mqueue", - Type: "bind", + Type: TypeBind, Source: "/dev/mqueue", Options: []string{"bind", "nosuid", "noexec", "nodev"}, } @@ -136,7 +164,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint g.RemoveMount("/proc") procMount := spec.Mount{ Destination: "/proc", - Type: "bind", + Type: TypeBind, Source: "/proc", Options: []string{"rbind", "nosuid", "noexec", "nodev"}, } @@ -377,6 +405,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts) //--mount configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts) + + // Split normal mounts and named volumes + newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts) + configSpec.Mounts = newMounts + config.NamedVolumes = namedVolumes + // BLOCK IO blkio, err := config.CreateBlockIO() if err != nil { diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 63d500204..8cd13e251 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -103,6 +103,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { VirtualSize: newImage.VirtualSize, Containers: int64(len(containers)), Labels: labels, + TopLayer: newImage.TopLayer(), } return call.ReplyGetImage(il) } @@ -923,3 +924,40 @@ func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { } return call.ReplyDiff(response) } + +// GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. +func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(layerInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyGetLayersMapWithImageInfo(string(b)) +} + +// BuildImageHierarchyMap ... +func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { + img, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + imageInfo := &image.InfoImage{ + ID: img.ID(), + Tags: img.Names(), + } + layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := image.BuildImageHierarchyMap(imageInfo, layerInfo, img.TopLayer()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + b, err := json.Marshal(imageInfo) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyBuildImageHierarchyMap(string(b)) +} diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index bf9c88de5..fe4ae64cf 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -144,7 +144,7 @@ var _ = Describe("Podman commit", func() { inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) image := inspect.InspectImageJSON() - _, ok := image[0].Config.Volumes["/tmp"] + _, ok := image[0].Config.Volumes["/foo"] Expect(ok).To(BeTrue()) r := podmanTest.Podman([]string{"run", "newimage"}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 2daf2fe5b..a89ee491b 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -611,7 +611,6 @@ USER mail` session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("data")) - }) It("podman run --volumes flag with multiple volumes", func() { diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 888474670..39628d56f 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -32,7 +32,7 @@ var _ = Describe("Podman volume rm", func() { }) - It("podman rm volume", func() { + It("podman volume rm", func() { session := podmanTest.Podman([]string{"volume", "create", "myvol"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -47,7 +47,7 @@ var _ = Describe("Podman volume rm", func() { Expect(len(session.OutputToStringArray())).To(Equal(0)) }) - It("podman rm with --force flag", func() { + It("podman volume rm with --force flag", func() { SkipIfRemote() session := podmanTest.Podman([]string{"create", "-v", "myvol:/myvol", ALPINE, "ls"}) cid := session.OutputToString() |