From 7bdfb4f9b361aca4f4f3337907feb3ca414d36e4 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan@redhat.com>
Date: Thu, 26 Apr 2018 17:21:48 +0200
Subject: podman: accept option --rootfs to use exploded images

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>

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