Add support for overlay volume mounts in podman.

Add support -v for overlay volume mounts in podman.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
Qi Wang
2020-07-09 15:46:14 -04:00
parent 17f9b80600
commit 020d81f113
15 changed files with 359 additions and 58 deletions

View File

@ -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})

View File

@ -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,24 +515,43 @@ 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
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 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),
@ -521,9 +559,10 @@ func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*sp
Options: options,
}
if _, ok := mounts[newMount.Destination]; ok {
return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
return nil, nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
}
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

View File

@ -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.
`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>

View File

@ -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.

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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))
}

View File

@ -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.

View File

@ -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")

View File

@ -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)))
})
})