summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/specgen.go3
-rw-r--r--cmd/podman/common/volumes.go95
-rw-r--r--docs/source/markdown/podman-create.1.md68
-rw-r--r--docs/source/markdown/podman-run.1.md43
-rw-r--r--libpod/container.go11
-rw-r--r--libpod/container_internal.go6
-rw-r--r--libpod/container_internal_linux.go18
-rw-r--r--libpod/container_internal_unsupported.go4
-rw-r--r--libpod/container_validate.go19
-rw-r--r--libpod/options.go29
-rw-r--r--libpod/runtime.go2
-rw-r--r--pkg/specgen/generate/container_create.go14
-rw-r--r--pkg/specgen/specgen.go12
-rw-r--r--pkg/util/mountOpts.go4
-rw-r--r--test/e2e/run_volume_test.go89
15 files changed, 359 insertions, 58 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 08099da4b..416c6f6ec 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -531,12 +531,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// Only add read-only tmpfs mounts in case that we are read-only and the
// read-only tmpfs flag has been set.
- mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
+ mounts, volumes, overlayVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
if err != nil {
return err
}
s.Mounts = mounts
s.Volumes = volumes
+ s.OverlayVolumes = overlayVolumes
for _, dev := range c.Devices {
s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go
index b201786f0..47a6f3c35 100644
--- a/cmd/podman/common/volumes.go
+++ b/cmd/podman/common/volumes.go
@@ -34,43 +34,43 @@ var (
// Does not handle image volumes, init, and --volumes-from flags.
// Can also add tmpfs mounts from read-only tmpfs.
// TODO: handle options parsing/processing via containers/storage/pkg/mount
-func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, error) {
+func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) {
// Get mounts from the --mounts flag.
unifiedMounts, unifiedVolumes, err := getMounts(mountFlag)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
// Next --volumes flag.
- volumeMounts, volumeVolumes, err := getVolumeMounts(volumeFlag)
+ volumeMounts, volumeVolumes, overlayVolumes, err := getVolumeMounts(volumeFlag)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
// Next --tmpfs flag.
tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
// Unify mounts from --mount, --volume, --tmpfs.
// Start with --volume.
for dest, mount := range volumeMounts {
if _, ok := unifiedMounts[dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
}
unifiedMounts[dest] = mount
}
for dest, volume := range volumeVolumes {
if _, ok := unifiedVolumes[dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
}
unifiedVolumes[dest] = volume
}
// Now --tmpfs
for dest, tmpfs := range tmpfsMounts {
if _, ok := unifiedMounts[dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
}
unifiedMounts[dest] = tmpfs
}
@@ -101,15 +101,29 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
}
}
- // Check for conflicts between named volumes and mounts
+ // Check for conflicts between named volumes, overlay volumes, and mounts
for dest := range unifiedMounts {
if _, ok := unifiedVolumes[dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ if _, ok := overlayVolumes[dest]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
}
}
for dest := range unifiedVolumes {
if _, ok := unifiedMounts[dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ if _, ok := overlayVolumes[dest]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+ for dest := range overlayVolumes {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
}
}
@@ -119,7 +133,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
if mount.Type == TypeBind {
absSrc, err := filepath.Abs(mount.Source)
if err != nil {
- return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ return nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
}
mount.Source = absSrc
}
@@ -129,8 +143,12 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo
for _, volume := range unifiedVolumes {
finalVolumes = append(finalVolumes, volume)
}
+ finalOverlayVolume := make([]*specgen.OverlayVolume, 0)
+ for _, volume := range overlayVolumes {
+ finalOverlayVolume = append(finalOverlayVolume, volume)
+ }
- return finalMounts, finalVolumes, nil
+ return finalMounts, finalVolumes, finalOverlayVolume, nil
}
// getMounts takes user-provided input from the --mount flag and creates OCI
@@ -465,9 +483,10 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
return newVolume, nil
}
-func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.OverlayVolume, error) {
mounts := make(map[string]spec.Mount)
volumes := make(map[string]*specgen.NamedVolume)
+ overlayVolumes := make(map[string]*specgen.OverlayVolume)
volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
@@ -481,7 +500,7 @@ func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*sp
splitVol := strings.Split(vol, ":")
if len(splitVol) > 3 {
- return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ return nil, nil, nil, errors.Wrapf(volumeFormatErr, vol)
}
src = splitVol[0]
@@ -496,34 +515,54 @@ func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*sp
}
if len(splitVol) > 2 {
if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
}
// Do not check source dir for anonymous volumes
if len(splitVol) > 1 {
if err := parse.ValidateVolumeHostDir(src); err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
}
if err := parse.ValidateVolumeCtrDir(dest); err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
cleanDest := filepath.Clean(dest)
if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
// This is not a named volume
- newMount := spec.Mount{
- Destination: cleanDest,
- Type: string(TypeBind),
- Source: src,
- Options: options,
+ overlayFlag := false
+ for _, o := range options {
+ if o == "O" {
+ overlayFlag = true
+ if len(options) > 1 {
+ return nil, nil, nil, errors.New("can't use 'O' with other options")
+ }
+ }
}
- if _, ok := mounts[newMount.Destination]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ if overlayFlag {
+ // This is a overlay volume
+ newOverlayVol := new(specgen.OverlayVolume)
+ newOverlayVol.Destination = cleanDest
+ newOverlayVol.Source = src
+ if _, ok := overlayVolumes[newOverlayVol.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, newOverlayVol.Destination)
+ }
+ overlayVolumes[newOverlayVol.Destination] = newOverlayVol
+ } else {
+ newMount := spec.Mount{
+ Destination: cleanDest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: options,
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
}
- mounts[newMount.Destination] = newMount
} else {
// This is a named volume
newNamedVol := new(specgen.NamedVolume)
@@ -532,7 +571,7 @@ func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*sp
newNamedVol.Options = options
if _, ok := volumes[newNamedVol.Dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
}
volumes[newNamedVol.Dest] = newNamedVol
}
@@ -540,7 +579,7 @@ func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*sp
logrus.Debugf("User mount %s:%s options %v", src, dest, options)
}
- return mounts, volumes, nil
+ return mounts, volumes, overlayVolumes, nil
}
// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 4466e6616..dd68f5c39 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -811,7 +811,7 @@ Create a tmpfs mount
Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
-$ podman run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
+$ podman create -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
This command mounts a `tmpfs` at `/tmp` within the container. The supported mount
options are the same as the Linux default `mount` flags. If you do not specify
@@ -887,15 +887,20 @@ Set the UTS mode for the container
Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman
bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman
-container. The `OPTIONS` are a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup>
+container. Similarly, `-v SOURCE-VOLUME:/CONTAINER-DIR` will mount the volume
+in the host to the container. If no such named volume exists, Podman will
+create one. The `OPTIONS` are a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup>
-* [rw|ro]
-* [z|Z]
-* [`[r]shared`|`[r]slave`|`[r]private`]
-* [`[r]bind`]
-* [`noexec`|`exec`]
-* [`nodev`|`dev`]
-* [`nosuid`|`suid`]
+The _options_ is a comma delimited list and can be:
+
+* **rw**|**ro**
+* **z**|**Z**
+* [**r**]**shared**|[**r**]**slave**|[**r**]**private**
+* [**r**]**bind**
+* [**no**]**exec**
+* [**no**]**dev**
+* [**no**]**suid**
+* [**O**]
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The volume
will be mounted into the container at this directory.
@@ -908,18 +913,22 @@ the container is removed via the `--rm` flag or `podman rm --volumes`.
If a volume source is specified, it must be a path on the host or the name of a
named volume. Host paths are allowed to be absolute or relative; relative paths
are resolved relative to the directory Podman is run in. Any source that does
-not begin with a `.` or `/` it will be treated as the name of a named volume.
+not begin with a `.` or `/` will be treated as the name of a named volume.
If a volume with that name does not exist, it will be created. Volumes created
-with names are not anonymous and are not removed by `--rm` and
-`podman rm --volumes`.
+with names are not anonymous. They are not removed by the `--rm` option and the
+`podman rm --volumes` command.
You can specify multiple **-v** options to mount one or more volumes into a
container.
-You can add `:ro` or `:rw` suffix to a volume to mount it read-only or
+ `Write Protected Volume Mounts`
+
+You can add `:ro` or `:rw` suffix to a volume to mount it read-only or
read-write mode, respectively. By default, the volumes are mounted read-write.
See examples.
+ `Labeling Volume Mounts`
+
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
@@ -933,6 +942,37 @@ content label. Shared volume labels allow all containers to read/write content.
The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
+ `Overlay Volume Mounts`
+
+ The `:O` flag tells Podman to mount the directory from the host as a
+temporary storage using the `overlay file system`. The container processes
+can modify content within the mountpoint which is stored in the
+container storage in a separate directory. In overlay terms, the source
+directory will be the lower, and the container storage directory will be the
+upper. Modifications to the mount point are destroyed when the container
+finishes executing, similar to a tmpfs mount point being unmounted.
+
+ Subsequent executions of the container will see the original source directory
+content, any changes from previous container executions no longer exists.
+
+ One use case of the overlay mount is sharing the package cache from the
+host into the container to allow speeding up builds.
+
+ Note:
+
+ - The `O` flag conflicts with other options listed above.
+Content mounted into the container is labeled with the private label.
+ On SELinux systems, labels in the source directory must be readable
+by the container label. Usually containers can read/execute `container_share_t`
+and can read/write `container_file_t`. If you can not change the labels on a
+source volume, SELinux container separation must be disabled for the container
+to work.
+ - The source directory mounted into the container with an overlay mount
+should not be modified, it can cause unexpected failures. It is recommended
+that you do not modify the directory until the container finishes running.
+
+ `Mounts propagation`
+
By default bind mounted volumes are `private`. That means any mounts done
inside container will not be visible on host and vice versa. One can change
this behavior by specifying a volume mount propagation property. Making a
@@ -1110,7 +1150,7 @@ b
NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`.
## SEE ALSO
-subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8), slirp4netns(1), fuse-overlayfs(1)
+**subgid**(5), **subuid**(5), **libpod.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1).
## HISTORY
October 2017, converted from Docker documentation to Podman by Dan Walsh for Podman <dwalsh@redhat.com>
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index de1d8aff6..c69f41a39 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -936,6 +936,7 @@ The _options_ is a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup>
* [**no**]**exec**
* [**no**]**dev**
* [**no**]**suid**
+* [**O**]
The _container-dir_ must be an absolute path.
@@ -947,7 +948,7 @@ the container is removed via the **--rm** flag or **podman rm --volumes**.
If a volume source is specified, it must be a path on the host or the name of a
named volume. Host paths are allowed to be absolute or relative; relative paths
are resolved relative to the directory Podman is run in. Any source that does
-not begin with a **.** or **/** it will be treated as the name of a named volume.
+not begin with a **.** or **/** will be treated as the name of a named volume.
If a volume with that name does not exist, it will be created. Volumes created
with names are not anonymous and are not removed by **--rm** and
**podman rm --volumes**.
@@ -958,6 +959,8 @@ container.
You can add **:ro** or **:rw** option to mount a volume in read-only or
read-write mode, respectively. By default, the volumes are mounted read-write.
+ `Labeling Volume Mounts`
+
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
@@ -969,9 +972,41 @@ objects on the shared volumes. The **z** option tells Podman that two containers
share the volume content. As a result, Podman labels the content with a shared
content label. Shared volume labels allow all containers to read/write content.
The **Z** option tells Podman to label the content with a private unshared label.
+
+ `Overlay Volume Mounts`
+
+ The `:O` flag tells Podman to mount the directory from the host as a
+temporary storage using the `overlay file system`. The container processes
+can modify content within the mountpoint which is stored in the
+container storage in a separate directory. In overlay terms, the source
+directory will be the lower, and the container storage directory will be the
+upper. Modifications to the mount point are destroyed when the container
+finishes executing, similar to a tmpfs mount point being unmounted.
+
+ Subsequent executions of the container will see the original source directory
+content, any changes from previous container executions no longer exists.
+
+ One use case of the overlay mount is sharing the package cache from the
+host into the container to allow speeding up builds.
+
+ Note:
+
+ - The `O` flag conflicts with other options listed above.
+Content mounted into the container is labeled with the private label.
+ On SELinux systems, labels in the source directory must be readable
+by the container label. Usually containers can read/execute `container_share_t`
+and can read/write `container_file_t`. If you can not change the labels on a
+source volume, SELinux container separation must be disabled for the container
+to work.
+ - The source directory mounted into the container with an overlay mount
+should not be modified, it can cause unexpected failures. It is recommended
+that you do not modify the directory until the container finishes running.
+
Only the current container can use a private volume.
-By default bind mounted volumes are **private**. That means any mounts done
+ `Mounts propagation`
+
+By default bind mounted volumes are `private`. That means any mounts done
inside container will not be visible on host and vice versa. One can change
this behavior by specifying a volume mount propagation property. Making a
volume shared mounts done under that volume inside container will be
@@ -1228,6 +1263,8 @@ considered as an orphan and wiped if you execute **podman volume prune**:
$ podman run -v /var/db:/data1 -i -t fedora bash
$ podman run -v data:/data2 -i -t fedora bash
+
+$ podman run -v /var/cache/dnf:/var/cache/dnf:O -ti fedora dnf -y update
```
Using **--mount** flags to mount a host directory as a container folder, specify
@@ -1398,8 +1435,6 @@ October 2017, converted from Docker documentation to Podman by Dan Walsh for Pod
November 2015, updated by Sally O'Malley <somalley@redhat.com>
-July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
-
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
April 2014, Originally compiled by William Henry <whenry@redhat.com> based on docker.com source material and internal work.
diff --git a/libpod/container.go b/libpod/container.go
index 1ca38ae7e..fda018640 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -262,6 +262,8 @@ type ContainerConfig struct {
Mounts []string `json:"mounts,omitempty"`
// NamedVolumes lists the named volumes to mount into the container.
NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"`
+ // OverlayVolumes lists the overlay volumes to mount into the container.
+ OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"`
// Security Config
@@ -449,6 +451,15 @@ type ContainerNamedVolume struct {
Options []string `json:"options,omitempty"`
}
+// ContainerOverlayVolume is a overlay volume that will be mounted into the
+// container. Each volume is a libpod Volume present in the state.
+type ContainerOverlayVolume struct {
+ // Destination is the absolute path where the mount will be placed in the container.
+ Dest string `json:"dest"`
+ // Source specifies the source path of the mount.
+ Source string `json:"source,omitempty"`
+}
+
// Config accessors
// Unlocked
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index a79b9e5a8..b2e23b3a8 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -1588,6 +1588,12 @@ func (c *Container) cleanupStorage() error {
}
}
+ if err := c.cleanupOverlayMounts(); err != nil {
+ // If the container can't remove content report the error
+ logrus.Errorf("Failed to cleanup overlay mounts for %s: %v", c.ID(), err)
+ cleanupErr = err
+ }
+
if c.config.Rootfs != "" {
return cleanupErr
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 255505416..1c21f2ff9 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -20,6 +20,7 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
+ "github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/secrets"
"github.com/containers/common/pkg/apparmor"
"github.com/containers/common/pkg/config"
@@ -319,6 +320,19 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}
}
+ // Add overlay volumes
+ for _, overlayVol := range c.config.OverlayVolumes {
+ contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to create TempDir in the %s directory", c.config.StaticDir)
+ }
+ overlayMount, err := overlay.Mount(contentDir, overlayVol.Source, overlayVol.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
+ if err != nil {
+ return nil, errors.Wrapf(err, "creating overlay failed %q", overlayVol.Source)
+ }
+ g.AddMount(overlayMount)
+ }
+
hasHomeSet := false
for _, s := range c.config.Spec.Process.Env {
if strings.HasPrefix(s, "HOME=") {
@@ -1642,3 +1656,7 @@ func (c *Container) copyTimezoneFile(zonePath string) (string, error) {
}
return localtimeCopy, err
}
+
+func (c *Container) cleanupOverlayMounts() error {
+ return overlay.CleanupContent(c.config.StaticDir)
+}
diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go
index e6d94104c..b58a5116b 100644
--- a/libpod/container_internal_unsupported.go
+++ b/libpod/container_internal_unsupported.go
@@ -46,6 +46,10 @@ func (c *Container) getOCICgroupPath() (string, error) {
return "", define.ErrNotImplemented
}
+func (c *Container) cleanupOverlayMounts() error {
+ return nil
+}
+
func (c *Container) getUserOverrides() *lookup.Overrides {
return nil
}
diff --git a/libpod/container_validate.go b/libpod/container_validate.go
index c02833359..666ad0aca 100644
--- a/libpod/container_validate.go
+++ b/libpod/container_validate.go
@@ -99,5 +99,24 @@ func (c *Container) validate() error {
return errors.Wrapf(define.ErrInvalidArg, "cannot add to /etc/hosts if using image's /etc/hosts")
}
+ // Check named volume and overlay volumes destination conflits
+ destinations := make(map[string]bool)
+ for _, vol := range c.config.NamedVolumes {
+ // 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(define.ErrInvalidArg, "two volumes found with destination %s", vol.Dest)
+ }
+ destinations[vol.Dest] = true
+ }
+ for _, vol := range c.config.OverlayVolumes {
+ // 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(define.ErrInvalidArg, "two volumes found with destination %s", vol.Dest)
+ }
+ destinations[vol.Dest] = true
+ }
+
return nil
}
diff --git a/libpod/options.go b/libpod/options.go
index 32748a3c1..40cf452db 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1380,17 +1380,7 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
return define.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(define.ErrInvalidArg, "two volumes found with destination %s", vol.Dest)
- }
- destinations[vol.Dest] = true
-
mountOpts, err := util.ProcessOptions(vol.Options, false, "")
if err != nil {
return errors.Wrapf(err, "error processing options for named volume %q mounted at %q", vol.Name, vol.Dest)
@@ -1407,6 +1397,25 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
}
}
+// WithOverlayVolumes adds the given overlay volumes to the container.
+func WithOverlayVolumes(volumes []*ContainerOverlayVolume) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+
+ for _, vol := range volumes {
+
+ ctr.config.OverlayVolumes = append(ctr.config.OverlayVolumes, &ContainerOverlayVolume{
+ Dest: vol.Dest,
+ Source: vol.Source,
+ })
+ }
+
+ return nil
+ }
+}
+
// WithHealthCheck adds the healthcheck to the container config
func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption {
return func(ctr *Container) error {
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 0075c0e13..b30933b0c 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -577,7 +577,7 @@ func (r *Runtime) Shutdown(force bool) error {
}
var lastError error
- // If no store was requested, it can bew nil and there is no need to
+ // If no store was requested, it can be nil and there is no need to
// attempt to shut it down
if r.store != nil {
if _, err := r.store.Shutdown(force); err != nil {
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index c1ceac69e..6dbc45c16 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -201,6 +201,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
for _, volume := range volumes {
destinations = append(destinations, volume.Dest)
}
+ for _, overlayVolume := range s.OverlayVolumes {
+ destinations = append(destinations, overlayVolume.Destination)
+ }
options = append(options, libpod.WithUserVolumes(destinations))
if len(volumes) != 0 {
@@ -215,6 +218,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
options = append(options, libpod.WithNamedVolumes(vols))
}
+ if len(s.OverlayVolumes) != 0 {
+ var vols []*libpod.ContainerOverlayVolume
+ for _, v := range s.OverlayVolumes {
+ vols = append(vols, &libpod.ContainerOverlayVolume{
+ Dest: v.Destination,
+ Source: v.Source,
+ })
+ }
+ options = append(options, libpod.WithOverlayVolumes(vols))
+ }
+
if s.Command != nil {
options = append(options, libpod.WithCommand(s.Command))
}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index a346a9742..c6079be33 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -198,6 +198,9 @@ type ContainerStorageConfig struct {
// there are conflicts.
// Optional.
Volumes []*NamedVolume `json:"volumes,omitempty"`
+ // Overlay volumes are named volumes that will be added to the container.
+ // Optional.
+ OverlayVolumes []*OverlayVolume `json:"overlay_volumes,omitempty"`
// Devices are devices that will be added to the container.
// Optional.
Devices []spec.LinuxDevice `json:"devices,omitempty"`
@@ -443,6 +446,15 @@ type NamedVolume struct {
Options []string
}
+// OverlayVolume holds information about a overlay volume that will be mounted into
+// the container.
+type OverlayVolume struct {
+ // Destination is the absolute path where the mount will be placed in the container.
+ Destination string `json:"destination"`
+ // Source specifies the source path of the mount.
+ Source string `json:"source,omitempty"`
+}
+
// PortMapping is one or more ports that will be mapped into the container.
type PortMapping struct {
// HostIP is the IP that we will bind to on the host.
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index 416e60728..eab2657e3 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -33,6 +33,10 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
// Some options have parameters - size, mode
splitOpt := strings.SplitN(opt, "=", 2)
switch splitOpt[0] {
+ case "O":
+ if len(options) > 1 {
+ return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options")
+ }
case "exec", "noexec":
if foundExec {
return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'noexec' and 'exec' can be used")
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
index 92988ff87..2b806ac2b 100644
--- a/test/e2e/run_volume_test.go
+++ b/test/e2e/run_volume_test.go
@@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"
+ "github.com/containers/libpod/v2/pkg/rootless"
. "github.com/containers/libpod/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -450,4 +451,92 @@ VOLUME /test/`
Expect(data[0].Mounts[0].Source).To(Equal("/tmp"))
Expect(data[0].Mounts[0].Destination).To(Equal("/test"))
})
+
+ It("podman run with overlay volume flag", func() {
+ if os.Getenv("container") != "" {
+ Skip("Overlay mounts not supported when running in a container")
+ }
+ if rootless.IsRootless() {
+ if _, err := exec.LookPath("fuse_overlay"); err != nil {
+ Skip("Fuse-Overlayfs required for rootless overlay mount test")
+ }
+ }
+ mountPath := filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ testFile := filepath.Join(mountPath, "test1")
+ f, err := os.Create(testFile)
+ f.Close()
+
+ // Make sure host directory gets mounted in to container as overlay
+ session := podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:O", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ found, matches := session.GrepString("/run/test")
+ Expect(found).Should(BeTrue())
+ Expect(matches[0]).To(ContainSubstring("overlay"))
+
+ // Make sure host files show up in the container
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:O", mountPath), ALPINE, "ls", "/run/test/test1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // Make sure modifications in container do not show up on host
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:O", mountPath), ALPINE, "touch", "/run/test/container"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ _, err = os.Stat(filepath.Join(mountPath, "container"))
+ Expect(err).To(Not(BeNil()))
+
+ // Make sure modifications in container disappear when container is stopped
+ session = podmanTest.Podman([]string{"create", "-d", "-v", fmt.Sprintf("%s:/run/test:O", mountPath), ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"start", "-l"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "-l", "touch", "/run/test/container"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "-l", "ls", "/run/test/container"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"stop", "-l"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"start", "-l"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "-l", "ls", "/run/test/container"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("overlay volume conflicts with named volume and mounts", func() {
+ mountPath := filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ testFile := filepath.Join(mountPath, "test1")
+ f, err := os.Create(testFile)
+ Expect(err).To(BeNil())
+ f.Close()
+ mountSrc := filepath.Join(podmanTest.TempDir, "vol-test1")
+ err = os.MkdirAll(mountSrc, 0755)
+ Expect(err).To(BeNil())
+ mountDest := "/run/test"
+ volName := "myvol"
+
+ session := podmanTest.Podman([]string{"volume", "create", volName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // overlay and named volume destinations conflict
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:%s:O", mountPath, mountDest), "-v", fmt.Sprintf("%s:%s", volName, mountDest), ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ // overlay and bind mount destinations conflict
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:%s:O", mountPath, mountDest), "--mount", fmt.Sprintf("type=bind,src=%s,target=%s", mountSrc, mountDest), ALPINE})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ // overlay and tmpfs mount destinations conflict
+ session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:%s:O", mountPath, mountDest), "--mount", fmt.Sprintf("type=tmpfs,target=%s", mountDest), ALPINE})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
})