From 3d395767d8c3e467e784e3836c7175f6d11931a7 Mon Sep 17 00:00:00 2001
From: umohnani8 <umohnani@redhat.com>
Date: Fri, 16 Feb 2018 10:38:12 -0500
Subject: Implement --image-volumes for create and run

--image-volumes tells podman what to do with the image volumes in the image config
There are 3 options: bind, tmpfs, and ignore
bind puts the volume contents in /var/lib/containers/storage/container-id/volumes/vol-dir
and bind mounts it into the container at /vol-dir
tmpfs mounts /vol-dir as a tmps into the container
ignore doesn't mount the image volumes onto the container

Signed-off-by: umohnani8 <umohnani@redhat.com>

Closes: #377
Approved by: rhatdan
---
 cmd/podman/common.go         |  5 +++++
 cmd/podman/create.go         | 42 ++++++++++++++++++++++++++++-------------
 cmd/podman/run.go            |  3 ++-
 cmd/podman/spec.go           | 24 +++++++++++++++++++++--
 cmd/podman/spec_test.go      |  2 +-
 completions/bash/podman      |  2 ++
 docs/podman-create.1.md      |  8 ++++++++
 docs/podman-run.1.md         |  8 ++++++++
 libpod/container_api.go      |  7 +++++++
 libpod/container_internal.go | 45 ++++++++++++++++++++++++++++++++++++++++++++
 libpod/util.go               | 13 ++++++++++++-
 11 files changed, 141 insertions(+), 18 deletions(-)

diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 657535e63..8dee6cb98 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -197,6 +197,11 @@ var createFlags = []cli.Flag{
 		Name:  "hostname",
 		Usage: "Set container hostname",
 	},
+	cli.StringFlag{
+		Name:  "image-volume, builtin-volume",
+		Usage: "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')",
+		Value: "bind",
+	},
 	cli.BoolFlag{
 		Name:  "interactive, i",
 		Usage: "Keep STDIN open even if not attached",
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 3d811b58b..810a5e3ed 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -89,6 +89,8 @@ type createConfig struct {
 	Hostname           string   //hostname
 	Image              string
 	ImageID            string
+	BuiltinImgVolumes  map[string]struct{}   // volumes defined in the image config
+	ImageVolumeType    string                // how to handle the image volume, either bind, tmpfs, or ignore
 	Interactive        bool                  //interactive
 	IpcMode            container.IpcMode     //ipc
 	IP6Address         string                //ipv6
@@ -180,6 +182,7 @@ func createCmd(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
+	useImageVolumes := createConfig.ImageVolumeType == "bind"
 
 	runtimeSpec, err := createConfigToOCISpec(createConfig)
 	if err != nil {
@@ -190,7 +193,7 @@ func createCmd(c *cli.Context) error {
 		return errors.Wrapf(err, "unable to parse new container options")
 	}
 	// Gather up the options for NewContainer which consist of With... funcs
-	options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
+	options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, useImageVolumes))
 	options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
 	options = append(options, libpod.WithLabels(createConfig.Labels))
 	options = append(options, libpod.WithUser(createConfig.User))
@@ -626,19 +629,32 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
 		return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
 	}
 
+	ImageVolumes := data.ContainerConfig.Volumes
+
+	var imageVolType = map[string]string{
+		"bind":   "",
+		"tmpfs":  "",
+		"ignore": "",
+	}
+	if _, ok := imageVolType[c.String("image-volume")]; !ok {
+		return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume"))
+	}
+
 	config := &createConfig{
-		Runtime:      runtime,
-		CapAdd:       c.StringSlice("cap-add"),
-		CapDrop:      c.StringSlice("cap-drop"),
-		CgroupParent: c.String("cgroup-parent"),
-		Command:      command,
-		Detach:       c.Bool("detach"),
-		Devices:      c.StringSlice("device"),
-		DNSOpt:       c.StringSlice("dns-opt"),
-		DNSSearch:    c.StringSlice("dns-search"),
-		DNSServers:   c.StringSlice("dns"),
-		Entrypoint:   entrypoint,
-		Env:          env,
+		Runtime:           runtime,
+		BuiltinImgVolumes: ImageVolumes,
+		ImageVolumeType:   c.String("image-volume"),
+		CapAdd:            c.StringSlice("cap-add"),
+		CapDrop:           c.StringSlice("cap-drop"),
+		CgroupParent:      c.String("cgroup-parent"),
+		Command:           command,
+		Detach:            c.Bool("detach"),
+		Devices:           c.StringSlice("device"),
+		DNSOpt:            c.StringSlice("dns-opt"),
+		DNSSearch:         c.StringSlice("dns-search"),
+		DNSServers:        c.StringSlice("dns"),
+		Entrypoint:        entrypoint,
+		Env:               env,
 		//ExposedPorts:   ports,
 		GroupAdd:       groupAdd,
 		Hostname:       c.String("hostname"),
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index f13e293bc..3d6175cef 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -54,6 +54,7 @@ func runCmd(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
+	useImageVolumes := createConfig.ImageVolumeType == "bind"
 
 	runtimeSpec, err := createConfigToOCISpec(createConfig)
 	if err != nil {
@@ -66,7 +67,7 @@ func runCmd(c *cli.Context) error {
 	}
 
 	// Gather up the options for NewContainer which consist of With... funcs
-	options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
+	options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, useImageVolumes))
 	options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
 	options = append(options, libpod.WithLabels(createConfig.Labels))
 	options = append(options, libpod.WithUser(createConfig.User))
diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go
index e78118b2f..2c2005399 100644
--- a/cmd/podman/spec.go
+++ b/cmd/podman/spec.go
@@ -351,7 +351,7 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
 	}
 
 	// BIND MOUNTS
-	mounts, err := config.GetVolumeMounts()
+	mounts, err := config.GetVolumeMounts(configSpec.Mounts)
 	if err != nil {
 		return nil, errors.Wrapf(err, "error getting volume mounts")
 	}
@@ -500,7 +500,7 @@ func getDefaultAnnotations() map[string]string {
 }
 
 //GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
-func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
+func (c *createConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) {
 	var m []spec.Mount
 	var options []string
 	for _, i := range c.Volumes {
@@ -509,6 +509,9 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
 		if len(spliti) > 2 {
 			options = strings.Split(spliti[2], ",")
 		}
+		if libpod.MountExists(specMounts, spliti[1]) {
+			continue
+		}
 		options = append(options, "rbind")
 		var foundrw, foundro, foundz, foundZ bool
 		var rootProp string
@@ -550,6 +553,23 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
 			Options:     options,
 		})
 	}
+
+	// volumes from image config
+	if c.ImageVolumeType != "tmpfs" {
+		return m, nil
+	}
+	for vol := range c.BuiltinImgVolumes {
+		if libpod.MountExists(specMounts, vol) {
+			continue
+		}
+		mount := spec.Mount{
+			Destination: vol,
+			Type:        string(TypeTmpfs),
+			Source:      string(TypeTmpfs),
+			Options:     []string{"rw", "noexec", "nosuid", "nodev", "tmpcopyup"},
+		}
+		m = append(m, mount)
+	}
 	return m, nil
 }
 
diff --git a/cmd/podman/spec_test.go b/cmd/podman/spec_test.go
index 768b079aa..1e1064df9 100644
--- a/cmd/podman/spec_test.go
+++ b/cmd/podman/spec_test.go
@@ -18,7 +18,7 @@ func TestCreateConfig_GetVolumeMounts(t *testing.T) {
 	config := createConfig{
 		Volumes: []string{"foobar:/foobar:ro"},
 	}
-	specMount, err := config.GetVolumeMounts()
+	specMount, err := config.GetVolumeMounts([]spec.Mount{})
 	assert.NoError(t, err)
 	assert.True(t, reflect.DeepEqual(data, specMount[0]))
 }
diff --git a/completions/bash/podman b/completions/bash/podman
index 905792a9b..6d9098fc9 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1042,6 +1042,7 @@ _podman_container_run() {
 		--attach -a
 		--blkio-weight
 		--blkio-weight-device
+		--builtin-volume
 		--cap-add
 		--cap-drop
 		--cgroup-parent
@@ -1068,6 +1069,7 @@ _podman_container_run() {
 		--expose
 		--group-add
 		--hostname -h
+		--image-volume
 		--init-path
 		--ip
 		--ip6
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 7d2b62fb0..98141221b 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -217,6 +217,14 @@ inside of the container.
 **--help**
   Print usage statement
 
+**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore*
+    Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind').
+    bind: A directory is created inside the container state directory and bind mounted into
+        the container for the volumes.
+    tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create
+        content that dissapears when the container is stopped.
+    ignore: All volumes are just ignored and no action is taken.
+
 **-i**, **--interactive**=*true*|*false*
    Keep STDIN open even if not attached. The default is *false*.
 
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index 53239422d..5e235514c 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -214,6 +214,14 @@ inside of the container.
 **--help**
    Print usage statement
 
+**--image-volume**, **builtin-volume**=*bind*|*tmpfs*|*ignore*
+    Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')
+    bind: A directory is created inside the container state directory and bind mounted into
+        the container for the volumes.
+    tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create
+        content that dissapears when the container is stopped.
+    ignore: All volumes are just ignored and no action is taken.
+
 **-i**, **--interactive**=*true*|*false*
    Keep STDIN open even if not attached. The default is *false*.
 
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 6fdc45589..149197470 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -144,6 +144,13 @@ func (c *Container) Init() (err error) {
 	}
 	g.AddMount(hostnameMnt)
 
+	// Bind builtin image volumes
+	if c.config.ImageVolumes {
+		if err = c.addImageVolumes(&g); err != nil {
+			return errors.Wrapf(err, "error mounting image volumes")
+		}
+	}
+
 	if c.config.User != "" {
 		if !c.state.Mounted {
 			return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 6e9852d1e..e22d36f99 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -12,10 +12,12 @@ import (
 
 	"github.com/containers/storage"
 	"github.com/containers/storage/pkg/archive"
+	"github.com/containers/storage/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/docker/docker/pkg/stringid"
 	spec "github.com/opencontainers/runtime-spec/specs-go"
+	"github.com/opencontainers/runtime-tools/generate"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -271,6 +273,49 @@ func (c *Container) export(path string) error {
 	return err
 }
 
+func (c *Container) addImageVolumes(g *generate.Generator) error {
+	mountPoint := c.state.Mountpoint
+	if !c.state.Mounted {
+		return errors.Wrapf(ErrInternal, "container is not mounted")
+	}
+
+	imageStorage, err := c.runtime.getImage(c.config.RootfsImageID)
+	if err != nil {
+		return err
+	}
+	imageData, err := c.runtime.getImageInspectInfo(*imageStorage)
+	if err != nil {
+		return err
+	}
+
+	for k := range imageData.ContainerConfig.Volumes {
+		mount := spec.Mount{
+			Destination: k,
+			Type:        "bind",
+			Options:     []string{"rbind", "rw"},
+		}
+		if MountExists(g.Mounts(), k) {
+			continue
+		}
+		volumePath := filepath.Join(c.config.StaticDir, "volumes", k)
+		if _, err := os.Stat(volumePath); os.IsNotExist(err) {
+			if err = os.MkdirAll(volumePath, 0755); err != nil {
+				return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID)
+			}
+			if err = label.Relabel(volumePath, c.config.MountLabel, false); err != nil {
+				return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, k, c.ID)
+			}
+			srcPath := filepath.Join(mountPoint, k)
+			if err = chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) {
+				return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, k, c.ID, srcPath)
+			}
+			mount.Source = volumePath
+		}
+		g.AddMount(mount)
+	}
+	return nil
+}
+
 // Get path of artifact with a given name for this container
 func (c *Container) getArtifactPath(name string) string {
 	return filepath.Join(c.config.StaticDir, artifactsDir, name)
diff --git a/libpod/util.go b/libpod/util.go
index 1a033a940..0c6700fbf 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -4,13 +4,14 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 
 	"github.com/containers/image/signature"
 	"github.com/containers/image/types"
+	spec "github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/pkg/errors"
-	"strconv"
 )
 
 // Runtime API constants
@@ -96,3 +97,13 @@ func RemoveScientificNotationFromFloat(x float64) (float64, error) {
 	}
 	return result, nil
 }
+
+// MountExists returns true if dest exists in the list of mounts
+func MountExists(specMounts []spec.Mount, dest string) bool {
+	for _, m := range specMounts {
+		if m.Destination == dest {
+			return true
+		}
+	}
+	return false
+}
-- 
cgit v1.2.3-54-g00ecf