mirror of
https://github.com/containers/podman.git
synced 2025-06-20 00:51:16 +08:00
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:
@ -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})
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
|
@ -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)))
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user