mirror of https://github.com/containers/podman.git
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
This commit is contained in:
parent
7a7a6c2d79
commit
3d395767d8
|
@ -197,6 +197,11 @@ var createFlags = []cli.Flag{
|
||||||
Name: "hostname",
|
Name: "hostname",
|
||||||
Usage: "Set container 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{
|
cli.BoolFlag{
|
||||||
Name: "interactive, i",
|
Name: "interactive, i",
|
||||||
Usage: "Keep STDIN open even if not attached",
|
Usage: "Keep STDIN open even if not attached",
|
||||||
|
|
|
@ -89,6 +89,8 @@ type createConfig struct {
|
||||||
Hostname string //hostname
|
Hostname string //hostname
|
||||||
Image string
|
Image string
|
||||||
ImageID 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
|
Interactive bool //interactive
|
||||||
IpcMode container.IpcMode //ipc
|
IpcMode container.IpcMode //ipc
|
||||||
IP6Address string //ipv6
|
IP6Address string //ipv6
|
||||||
|
@ -180,6 +182,7 @@ func createCmd(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
useImageVolumes := createConfig.ImageVolumeType == "bind"
|
||||||
|
|
||||||
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -190,7 +193,7 @@ func createCmd(c *cli.Context) error {
|
||||||
return errors.Wrapf(err, "unable to parse new container options")
|
return errors.Wrapf(err, "unable to parse new container options")
|
||||||
}
|
}
|
||||||
// Gather up the options for NewContainer which consist of With... funcs
|
// 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.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
||||||
options = append(options, libpod.WithLabels(createConfig.Labels))
|
options = append(options, libpod.WithLabels(createConfig.Labels))
|
||||||
options = append(options, libpod.WithUser(createConfig.User))
|
options = append(options, libpod.WithUser(createConfig.User))
|
||||||
|
@ -626,8 +629,21 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
|
||||||
return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
|
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{
|
config := &createConfig{
|
||||||
Runtime: runtime,
|
Runtime: runtime,
|
||||||
|
BuiltinImgVolumes: ImageVolumes,
|
||||||
|
ImageVolumeType: c.String("image-volume"),
|
||||||
CapAdd: c.StringSlice("cap-add"),
|
CapAdd: c.StringSlice("cap-add"),
|
||||||
CapDrop: c.StringSlice("cap-drop"),
|
CapDrop: c.StringSlice("cap-drop"),
|
||||||
CgroupParent: c.String("cgroup-parent"),
|
CgroupParent: c.String("cgroup-parent"),
|
||||||
|
|
|
@ -54,6 +54,7 @@ func runCmd(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
useImageVolumes := createConfig.ImageVolumeType == "bind"
|
||||||
|
|
||||||
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -66,7 +67,7 @@ func runCmd(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather up the options for NewContainer which consist of With... funcs
|
// 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.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
||||||
options = append(options, libpod.WithLabels(createConfig.Labels))
|
options = append(options, libpod.WithLabels(createConfig.Labels))
|
||||||
options = append(options, libpod.WithUser(createConfig.User))
|
options = append(options, libpod.WithUser(createConfig.User))
|
||||||
|
|
|
@ -351,7 +351,7 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BIND MOUNTS
|
// BIND MOUNTS
|
||||||
mounts, err := config.GetVolumeMounts()
|
mounts, err := config.GetVolumeMounts(configSpec.Mounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error getting volume mounts")
|
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
|
//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 m []spec.Mount
|
||||||
var options []string
|
var options []string
|
||||||
for _, i := range c.Volumes {
|
for _, i := range c.Volumes {
|
||||||
|
@ -509,6 +509,9 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
|
||||||
if len(spliti) > 2 {
|
if len(spliti) > 2 {
|
||||||
options = strings.Split(spliti[2], ",")
|
options = strings.Split(spliti[2], ",")
|
||||||
}
|
}
|
||||||
|
if libpod.MountExists(specMounts, spliti[1]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
options = append(options, "rbind")
|
options = append(options, "rbind")
|
||||||
var foundrw, foundro, foundz, foundZ bool
|
var foundrw, foundro, foundz, foundZ bool
|
||||||
var rootProp string
|
var rootProp string
|
||||||
|
@ -550,6 +553,23 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
|
||||||
Options: options,
|
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
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestCreateConfig_GetVolumeMounts(t *testing.T) {
|
||||||
config := createConfig{
|
config := createConfig{
|
||||||
Volumes: []string{"foobar:/foobar:ro"},
|
Volumes: []string{"foobar:/foobar:ro"},
|
||||||
}
|
}
|
||||||
specMount, err := config.GetVolumeMounts()
|
specMount, err := config.GetVolumeMounts([]spec.Mount{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, reflect.DeepEqual(data, specMount[0]))
|
assert.True(t, reflect.DeepEqual(data, specMount[0]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1042,6 +1042,7 @@ _podman_container_run() {
|
||||||
--attach -a
|
--attach -a
|
||||||
--blkio-weight
|
--blkio-weight
|
||||||
--blkio-weight-device
|
--blkio-weight-device
|
||||||
|
--builtin-volume
|
||||||
--cap-add
|
--cap-add
|
||||||
--cap-drop
|
--cap-drop
|
||||||
--cgroup-parent
|
--cgroup-parent
|
||||||
|
@ -1068,6 +1069,7 @@ _podman_container_run() {
|
||||||
--expose
|
--expose
|
||||||
--group-add
|
--group-add
|
||||||
--hostname -h
|
--hostname -h
|
||||||
|
--image-volume
|
||||||
--init-path
|
--init-path
|
||||||
--ip
|
--ip
|
||||||
--ip6
|
--ip6
|
||||||
|
|
|
@ -217,6 +217,14 @@ inside of the container.
|
||||||
**--help**
|
**--help**
|
||||||
Print usage statement
|
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*
|
**-i**, **--interactive**=*true*|*false*
|
||||||
Keep STDIN open even if not attached. The default is *false*.
|
Keep STDIN open even if not attached. The default is *false*.
|
||||||
|
|
||||||
|
|
|
@ -214,6 +214,14 @@ inside of the container.
|
||||||
**--help**
|
**--help**
|
||||||
Print usage statement
|
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*
|
**-i**, **--interactive**=*true*|*false*
|
||||||
Keep STDIN open even if not attached. The default is *false*.
|
Keep STDIN open even if not attached. The default is *false*.
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,13 @@ func (c *Container) Init() (err error) {
|
||||||
}
|
}
|
||||||
g.AddMount(hostnameMnt)
|
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.config.User != "" {
|
||||||
if !c.state.Mounted {
|
if !c.state.Mounted {
|
||||||
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
||||||
|
|
|
@ -12,10 +12,12 @@ import (
|
||||||
|
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
|
"github.com/containers/storage/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/opencontainers/runtime-tools/generate"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -271,6 +273,49 @@ func (c *Container) export(path string) error {
|
||||||
return err
|
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
|
// Get path of artifact with a given name for this container
|
||||||
func (c *Container) getArtifactPath(name string) string {
|
func (c *Container) getArtifactPath(name string) string {
|
||||||
return filepath.Join(c.config.StaticDir, artifactsDir, name)
|
return filepath.Join(c.config.StaticDir, artifactsDir, name)
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/image/signature"
|
"github.com/containers/image/signature"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime API constants
|
// Runtime API constants
|
||||||
|
@ -96,3 +97,13 @@ func RemoveScientificNotationFromFloat(x float64) (float64, error) {
|
||||||
}
|
}
|
||||||
return result, nil
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue