diff --git a/docs/source/markdown/options/mount.md b/docs/source/markdown/options/mount.md index 9a14b39fd1..7114d74952 100644 --- a/docs/source/markdown/options/mount.md +++ b/docs/source/markdown/options/mount.md @@ -41,6 +41,8 @@ Options specific to type=**image**: - *rw*, *readwrite*: *true* or *false* (default if unspecified: *false*). +- *subpath*: Mount only a specific path within the image, instead of the whole image. + Options specific to **bind** and **glob**: - *ro*, *readonly*: *true* or *false* (default if unspecified: *false*). diff --git a/libpod/container.go b/libpod/container.go index 00616ccfa3..8e188b33c4 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -275,6 +275,8 @@ type ContainerImageVolume struct { Dest string `json:"dest"` // ReadWrite sets the volume writable. ReadWrite bool `json:"rw"` + // SubPath determines which part of the image will be mounted into the container. + SubPath string `json:"subPath,omitempty"` } // ContainerSecret is a secret that is mounted in a container diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go index 2ec7c0040e..78dd31b39c 100644 --- a/libpod/container_internal_common.go +++ b/libpod/container_internal_common.go @@ -459,11 +459,23 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc return nil, nil, fmt.Errorf("failed to create TempDir in the %s directory: %w", c.config.StaticDir, err) } + imagePath := mountPoint + if volume.SubPath != "" { + safeMount, err := c.safeMountSubPath(mountPoint, volume.SubPath) + if err != nil { + return nil, nil, err + } + + safeMounts = append(safeMounts, safeMount) + + imagePath = safeMount.mountPoint + } + var overlayMount spec.Mount if volume.ReadWrite { - overlayMount, err = overlay.Mount(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + overlayMount, err = overlay.Mount(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) } else { - overlayMount, err = overlay.MountReadOnly(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + overlayMount, err = overlay.MountReadOnly(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) } if err != nil { return nil, nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err) diff --git a/libpod/options.go b/libpod/options.go index f4f03f341c..c90d779bde 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1474,6 +1474,7 @@ func WithImageVolumes(volumes []*ContainerImageVolume) CtrCreateOption { Dest: vol.Dest, Source: vol.Source, ReadWrite: vol.ReadWrite, + SubPath: vol.SubPath, }) } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 0887eb4c8c..8d4029114b 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -501,6 +501,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l Dest: v.Destination, Source: v.Source, ReadWrite: v.ReadWrite, + SubPath: v.SubPath, }) } options = append(options, libpod.WithImageVolumes(vols)) diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index 075711138c..d2c1e54876 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -53,6 +53,9 @@ type ImageVolume struct { Destination string // ReadWrite sets the volume writable. ReadWrite bool + // SubPath mounts a particular path within the image. + // If empty, the whole image is mounted. + SubPath string `json:"subPath,omitempty"` } // GenVolumeMounts parses user input into mounts, volumes and overlay volumes diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go index c481867163..510b11254b 100644 --- a/pkg/specgenutil/volumes.go +++ b/pkg/specgenutil/volumes.go @@ -611,6 +611,14 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) { default: return nil, fmt.Errorf("invalid rw value %q: %w", value, util.ErrBadMntOption) } + case "subpath": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if !filepath.IsAbs(value) { + return nil, fmt.Errorf("volume subpath %q must be an absolute path", value) + } + newVolume.SubPath = value case "consistency": // Often used on MACs and mistakenly on Linux platforms. // Since Docker ignores this option so shall we. diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 2cd1881e8e..07cfca1fb3 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -934,4 +934,20 @@ USER testuser`, CITEST_IMAGE) Expect(run).Should(ExitCleanly()) Expect(run.OutputToString()).Should(ContainSubstring(strings.TrimLeft("/vol/", f.Name()))) }) + + It("podman run --mount type=image with subpath", func() { + ctrCommand := []string{"run", "--mount", fmt.Sprintf("type=image,source=%s,dest=/mnt,subpath=/etc", ALPINE), ALPINE, "ls"} + + run1Cmd := append(ctrCommand, "/etc") + run1 := podmanTest.Podman(run1Cmd) + run1.WaitWithDefaultTimeout() + Expect(run1).Should(ExitCleanly()) + + run2Cmd := append(ctrCommand, "/mnt") + run2 := podmanTest.Podman(run2Cmd) + run2.WaitWithDefaultTimeout() + Expect(run2).Should(ExitCleanly()) + + Expect(run1.OutputToString()).Should(Equal(run2.OutputToString())) + }) })