From 7bdfb4f9b361aca4f4f3337907feb3ca414d36e4 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 26 Apr 2018 17:21:48 +0200 Subject: podman: accept option --rootfs to use exploded images Signed-off-by: Giuseppe Scrivano Closes: #871 Approved by: mheon --- cmd/podman/common.go | 4 ++ cmd/podman/create.go | 97 +++++++++++++++++++++++++++++++------------- cmd/podman/run.go | 39 ++++++++++++------ completions/bash/podman | 1 + docs/podman-create.1.md | 7 ++++ docs/podman-run.1.md | 7 ++++ libpod/container.go | 2 + libpod/container_commit.go | 4 ++ libpod/container_inspect.go | 1 + libpod/container_internal.go | 38 +++++++++++++---- libpod/options.go | 21 ++++++++++ libpod/storage.go | 64 ++++++++++++++--------------- pkg/inspect/inspect.go | 1 + pkg/spec/createconfig.go | 1 + 14 files changed, 207 insertions(+), 80 deletions(-) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f38348a65..f647f9c4d 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -322,6 +322,10 @@ var createFlags = []cli.Flag{ Name: "rm", Usage: "Remove container (and pod if created) after exit", }, + cli.BoolFlag{ + Name: "rootfs", + Usage: "The first argument is not an image but the rootfs to the exploded container", + }, cli.StringSliceFlag{ Name: "security-opt", Usage: "Security Options (default [])", diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 8d66beab0..a0c1ec3f0 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -72,6 +72,11 @@ func createCmd(c *cli.Context) error { return errors.Errorf("image name or ID is required") } + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.Args()[0] + } + mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) if err != nil { return err @@ -89,12 +94,17 @@ func createCmd(c *cli.Context) error { rtc := runtime.GetConfig() ctx := getContext() - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) - if err != nil { - return err + imageName := "" + var data *inspect.ImageData = nil + if rootfs == "" { + newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) + if err != nil { + return err + } + data, err = newImage.Inspect(ctx) + imageName = newImage.Names()[0] } - data, err := newImage.Inspect(ctx) - createConfig, err := parseCreateOpts(ctx, c, runtime, newImage.Names()[0], data) + createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data) if err != nil { return err } @@ -118,6 +128,9 @@ func createCmd(c *cli.Context) error { options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithGroups(createConfig.GroupAdd)) options = append(options, libpod.WithIDMappings(*createConfig.IDMappings)) + if createConfig.Rootfs != "" { + options = append(options, libpod.WithRootFS(createConfig.Rootfs)) + } ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...) if err != nil { return err @@ -246,10 +259,16 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, err } - imageID := data.ID + imageID := "" + + inputCommand = c.Args()[1:] + if data != nil { + imageID = data.ID + } - if len(c.Args()) > 1 { - inputCommand = c.Args()[1:] + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.Args()[0] } sysctl, err := validateSysctl(c.StringSlice("sysctl")) @@ -337,12 +356,19 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // USER user := c.String("user") if user == "" { - user = data.ContainerConfig.User + if data == nil { + user = "0" + } else { + user = data.ContainerConfig.User + } } // STOP SIGNAL stopSignal := syscall.SIGTERM - signalString := data.ContainerConfig.StopSignal + signalString := "SIGTERM" + if data != nil { + signalString = data.ContainerConfig.StopSignal + } if c.IsSet("stop-signal") { signalString = c.String("stop-signal") } @@ -355,12 +381,14 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // ENVIRONMENT VARIABLES env := defaultEnvVariables - for _, e := range data.ContainerConfig.Env { - split := strings.SplitN(e, "=", 2) - if len(split) > 1 { - env[split[0]] = split[1] - } else { - env[split[0]] = "" + if data != nil { + for _, e := range data.ContainerConfig.Env { + split := strings.SplitN(e, "=", 2) + if len(split) > 1 { + env[split[0]] = split[1] + } else { + env[split[0]] = "" + } } } if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil { @@ -372,9 +400,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if err != nil { return nil, errors.Wrapf(err, "unable to process labels") } - for key, val := range data.ContainerConfig.Labels { - if _, ok := labels[key]; !ok { - labels[key] = val + if data != nil { + for key, val := range data.ContainerConfig.Labels { + if _, ok := labels[key]; !ok { + labels[key] = val + } } } @@ -386,9 +416,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if tty { annotations[ann.TTY] = "true" } - // Next, add annotations from the image - for key, value := range data.Annotations { - annotations[key] = value + if data != nil { + // Next, add annotations from the image + for key, value := range data.Annotations { + annotations[key] = value + } } // Last, add user annotations for _, annotation := range c.StringSlice("annotation") { @@ -403,14 +435,14 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim workDir := "/" if c.IsSet("workdir") || c.IsSet("w") { workDir = c.String("workdir") - } else if data.ContainerConfig.WorkingDir != "" { + } else if data != nil && data.ContainerConfig.WorkingDir != "" { workDir = data.ContainerConfig.WorkingDir } // ENTRYPOINT // User input entrypoint takes priority over image entrypoint entrypoint := c.StringSlice("entrypoint") - if len(entrypoint) == 0 { + if len(entrypoint) == 0 && data != nil { entrypoint = data.ContainerConfig.Entrypoint } // if entrypoint=, we need to clear the entrypoint @@ -425,7 +457,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if len(inputCommand) > 0 { // User command overrides data CMD command = append(command, inputCommand...) - } else if len(data.ContainerConfig.Cmd) > 0 && !c.IsSet("entrypoint") { + } else if data != nil && len(data.ContainerConfig.Cmd) > 0 && !c.IsSet("entrypoint") { // If not user command, add CMD command = append(command, data.ContainerConfig.Cmd...) } @@ -435,9 +467,12 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } // EXPOSED PORTS - portBindings, err := cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) - if err != nil { - return nil, err + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts) + if err != nil { + return nil, err + } } // SHM Size @@ -472,8 +507,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim return nil, err } } - ImageVolumes := data.ContainerConfig.Volumes + var ImageVolumes map[string]struct{} + if data != nil { + ImageVolumes = data.ContainerConfig.Volumes + } var imageVolType = map[string]string{ "bind": "", "tmpfs": "", @@ -567,6 +605,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim UsernsMode: usernsMode, Volumes: c.StringSlice("volume"), WorkDir: workDir, + Rootfs: rootfs, } if !config.Privileged { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3e7dc2303..2131df7ab 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -14,6 +14,7 @@ import ( "github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/libpod/image" + "github.com/projectatomic/libpod/pkg/inspect" cc "github.com/projectatomic/libpod/pkg/spec" "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" @@ -66,25 +67,36 @@ func runCmd(c *cli.Context) error { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) + if len(c.Args()) < 1 { return errors.Errorf("image name or ID is required") } + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.Args()[0] + } + ctx := getContext() rtc := runtime.GetConfig() - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) - if err != nil { - return errors.Wrapf(err, "unable to find image") - } - data, err := newImage.Inspect(ctx) - if err != nil { - return err - } - if len(newImage.Names()) < 1 { - imageName = newImage.ID() - } else { - imageName = newImage.Names()[0] + var newImage *image.Image = nil + var data *inspect.ImageData = nil + if rootfs == "" { + newImage, err = runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false) + if err != nil { + return errors.Wrapf(err, "unable to find image") + } + + data, err = newImage.Inspect(ctx) + if err != nil { + return err + } + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } } createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data) if err != nil { @@ -112,6 +124,9 @@ func runCmd(c *cli.Context) error { options = append(options, libpod.WithShmSize(createConfig.Resources.ShmSize)) options = append(options, libpod.WithGroups(createConfig.GroupAdd)) options = append(options, libpod.WithIDMappings(*createConfig.IDMappings)) + if createConfig.Rootfs != "" { + options = append(options, libpod.WithRootFS(createConfig.Rootfs)) + } // Default used if not overridden on command line diff --git a/completions/bash/podman b/completions/bash/podman index cd5ff04f7..292aa4b4b 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1431,6 +1431,7 @@ _podman_container_run() { --pids-limit --publish -p --runtime + --rootfs --security-opt --shm-size --stop-signal diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 93e085af5..e22efe4f3 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -470,6 +470,13 @@ its root filesystem mounted as read only prohibiting any writes. Automatically remove the container when it exits. The default is *false*. +**--rootfs** + +If specified, the first argument refers to an exploded container on the file system. + +This is useful to run a container without requiring any image management, the rootfs +of the container is assumed to be managed externally. + **--security-opt**=[] Security Options diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 4e254576c..3630a0558 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -491,6 +491,13 @@ its root filesystem mounted as read only prohibiting any writes. Automatically remove the container when it exits. The default is *false*. +**--rootfs** + +If specified, the first argument refers to an exploded container on the file system. + +This is useful to run a container without requiring any image management, the rootfs +of the container is assumed to be managed externally. + **--security-opt**=[] Security Options diff --git a/libpod/container.go b/libpod/container.go index 85b6fa3dd..26232e5c0 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -197,6 +197,8 @@ type ContainerConfig struct { // Information on the image used for the root filesystem/ RootfsImageID string `json:"rootfsImageID,omitempty"` RootfsImageName string `json:"rootfsImageName,omitempty"` + // Rootfs to use for the container, this conflicts with RootfsImageID + Rootfs string `json:"rootfs,omitempty"` // Whether to mount volumes specified in the image. ImageVolumes bool `json:"imageVolumes"` // Src path to be mounted on /dev/shm in container. diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 2872012b8..929850cbe 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -34,6 +34,10 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai isEnvCleared, isLabelCleared, isExposeCleared, isVolumeCleared bool ) + if c.config.Rootfs != "" { + return nil, errors.Errorf("cannot commit a container that uses an exploded rootfs") + } + if !c.batched { c.lock.Lock() defer c.lock.Unlock() diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index c8bf1a8cd..0e37036ca 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -70,6 +70,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) }, ImageID: config.RootfsImageID, ImageName: config.RootfsImageName, + Rootfs: config.Rootfs, ResolvConfPath: resolvPath, HostnamePath: hostnamePath, HostsPath: hostsPath, diff --git a/libpod/container_internal.go b/libpod/container_internal.go index e25ffaa03..b3e474836 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -56,6 +56,10 @@ var ( // mutable layer, and the rest is the RootFS: the set of immutable layers // that make up the image on which the container is based. func (c *Container) rootFsSize() (int64, error) { + if c.config.Rootfs != "" { + return 0, nil + } + container, err := c.runtime.store.Container(c.ID()) if err != nil { return 0, err @@ -93,6 +97,18 @@ func (c *Container) rootFsSize() (int64, error) { // rwSize Gets the size of the mutable top layer of the container. func (c *Container) rwSize() (int64, error) { + if c.config.Rootfs != "" { + var size int64 + err := filepath.Walk(c.config.Rootfs, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + size += info.Size() + return nil + }) + return size, err + } + container, err := c.runtime.store.Container(c.ID()) if err != nil { return 0, err @@ -205,7 +221,7 @@ func (c *Container) setupStorage(ctx context.Context) error { } // Need both an image ID and image name, plus a bool telling us whether to use the image configuration - if c.config.RootfsImageID == "" || c.config.RootfsImageName == "" { + if c.config.Rootfs == "" && (c.config.RootfsImageID == "" || c.config.RootfsImageName == "") { return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image") } @@ -691,9 +707,12 @@ func (c *Container) mountStorage() (err error) { } } - mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID()) - if err != nil { - return errors.Wrapf(err, "error mounting storage for container %s", c.ID()) + mountPoint := c.config.Rootfs + if mountPoint == "" { + mountPoint, err = c.runtime.storageService.MountContainerImage(c.ID()) + if err != nil { + return errors.Wrapf(err, "error mounting storage for container %s", c.ID()) + } } c.state.Mounted = true c.state.Mountpoint = mountPoint @@ -796,6 +815,9 @@ func (c *Container) cleanupStorage() error { } } } + if c.config.Rootfs != "" { + return nil + } // Also unmount storage if err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil { @@ -1154,7 +1176,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } // Bind builtin image volumes - if c.config.ImageVolumes { + if c.config.Rootfs == "" && c.config.ImageVolumes { if err := c.addImageVolumes(ctx, &g); err != nil { return nil, errors.Wrapf(err, "error mounting image volumes") } @@ -1235,8 +1257,10 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } - if err := idtools.MkdirAllAs(c.state.RealMountpoint, 0700, c.RootUID(), c.RootGID()); err != nil { - return nil, err + if c.config.Rootfs == "" { + if err := idtools.MkdirAllAs(c.state.RealMountpoint, 0700, c.RootUID(), c.RootGID()); err != nil { + return nil, err + } } g.SetRootPath(c.state.RealMountpoint) diff --git a/libpod/options.go b/libpod/options.go index 34bde3211..02bcb8628 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -2,6 +2,7 @@ package libpod import ( "net" + "os" "path/filepath" "regexp" "syscall" @@ -361,6 +362,9 @@ func WithRootFSFromImage(imageID string, imageName string, useImageVolumes bool) if ctr.config.RootfsImageID != "" || ctr.config.RootfsImageName != "" { return errors.Wrapf(ErrInvalidArg, "container already configured with root filesystem") } + if ctr.config.Rootfs != "" { + return errors.Wrapf(ErrInvalidArg, "cannot set both an image ID and a rootfs for a container") + } ctr.config.RootfsImageID = imageID ctr.config.RootfsImageName = imageName @@ -909,6 +913,23 @@ func WithCommand(command []string) CtrCreateOption { } } +// WithRootFS sets the rootfs for the container +func WithRootFS(rootfs string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + if _, err := os.Stat(rootfs); err != nil { + return errors.Wrapf(err, "error checking path %q", rootfs) + } + if ctr.config.RootfsImageID != "" { + return errors.Wrapf(ErrInvalidArg, "cannot set both an image ID and a rootfs for a container") + } + ctr.config.Rootfs = rootfs + return nil + } +} + // Pod Creation Options // WithPodName sets the name of the pod. diff --git a/libpod/storage.go b/libpod/storage.go index c0391326c..c5581d53c 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -60,40 +60,40 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { // CreateContainerStorage creates the storage end of things. We already have the container spec created // TO-DO We should be passing in an Image object in the future. func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string, options *storage.ContainerOptions) (ContainerInfo, error) { - var ref types.ImageReference - if imageName == "" && imageID == "" { - return ContainerInfo{}, ErrEmptyID - } - if containerName == "" { - return ContainerInfo{}, ErrEmptyID - } - // Check if we have the specified image. - ref, err := istorage.Transport.ParseStoreReference(r.store, imageID) - if err != nil { - return ContainerInfo{}, err - } - img, err := istorage.Transport.GetStoreImage(r.store, ref) - if err != nil { - return ContainerInfo{}, err - } - // Pull out a copy of the image's configuration. - image, err := ref.NewImage(ctx, systemContext) - if err != nil { - return ContainerInfo{}, err - } - defer image.Close() + var imageConfig *v1.Image + if imageName != "" { + var ref types.ImageReference + if containerName == "" { + return ContainerInfo{}, ErrEmptyID + } + // Check if we have the specified image. + ref, err := istorage.Transport.ParseStoreReference(r.store, imageID) + if err != nil { + return ContainerInfo{}, err + } + img, err := istorage.Transport.GetStoreImage(r.store, ref) + if err != nil { + return ContainerInfo{}, err + } + // Pull out a copy of the image's configuration. + image, err := ref.NewImage(ctx, systemContext) + if err != nil { + return ContainerInfo{}, err + } + defer image.Close() - // Get OCI configuration of image - imageConfig, err := image.OCIConfig(ctx) - if err != nil { - return ContainerInfo{}, err - } + // Get OCI configuration of image + imageConfig, err = image.OCIConfig(ctx) + if err != nil { + return ContainerInfo{}, err + } - // Update the image name and ID. - if imageName == "" && len(img.Names) > 0 { - imageName = img.Names[0] + // Update the image name and ID. + if imageName == "" && len(img.Names) > 0 { + imageName = img.Names[0] + } + imageID = img.ID } - imageID = img.ID // Build metadata to store with the container. metadata := RuntimeContainerMetadata{ @@ -119,7 +119,7 @@ func (r *storageService) CreateContainerStorage(ctx context.Context, systemConte }, } } - container, err := r.store.CreateContainer(containerID, names, img.ID, "", string(mdata), options) + container, err := r.store.CreateContainer(containerID, names, imageID, "", string(mdata), options) if err != nil { logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err) diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index ecc167544..09ddf488c 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -148,6 +148,7 @@ type ContainerInspectData struct { State *ContainerInspectState `json:"State"` ImageID string `json:"Image"` ImageName string `json:"ImageName"` + Rootfs string `json:"Rootfs"` ResolvConfPath string `json:"ResolvConfPath"` HostnamePath string `json:"HostnamePath"` HostsPath string `json:"HostsPath"` diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 25a0e26bd..4d29fb3bd 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -130,6 +130,7 @@ type CreateConfig struct { ApparmorProfile string //SecurityOpts SeccompProfilePath string //SecurityOpts SecurityOpts []string + Rootfs string } func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } -- cgit v1.2.3-54-g00ecf