diff --git a/docs/source/markdown/podman-kube-play.1.md.in b/docs/source/markdown/podman-kube-play.1.md.in index ea2ebf4a04..21a929a137 100644 --- a/docs/source/markdown/podman-kube-play.1.md.in +++ b/docs/source/markdown/podman-kube-play.1.md.in @@ -158,6 +158,17 @@ spec: and as a result environment variable `FOO` is set to `bar` for container `container-1`. +`Automounting Volumes` + +An image can be automatically mounted into a container if the annotation `io.podman.annotations.kube.image.automount/$ctrname` is given. The following rules apply: + +- The image must already exist locally. +- The image must have at least 1 volume directive. +- The path given by the volume directive will be mounted from the image into the container. For example, an image with a volume at `/test/test_dir` will have `/test/test_dir` in the image mounted to `/test/test_dir` in the container. +- Multiple images can be specified. If multiple images have a volume at a specific path, the last image specified trumps. +- The images are always mounted read-only. +- Images to mount are defined in the annotation "io.podman.annotations.kube.image.automount/$ctrname" as a semicolon-separated list. They are mounted into a single container in the pod, not the whole pod. The annotation can be specified for additional containers if additional mounts are required. + ## OPTIONS @@option annotation.container diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go index a9d4031ae2..ac1956f56b 100644 --- a/libpod/define/annotations.go +++ b/libpod/define/annotations.go @@ -160,6 +160,9 @@ const ( // the k8s behavior of waiting for the intialDelaySeconds to be over before updating the status KubeHealthCheckAnnotation = "io.podman.annotations.kube.health.check" + // KubeImageAutomountAnnotation + KubeImageAutomountAnnotation = "io.podman.annotations.kube.image.volumes.mount" + // TotalAnnotationSizeLimitB is the max length of annotations allowed by Kubernetes. TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB ) diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index f1202dfce6..fa7ffb8eab 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -126,6 +126,54 @@ func (ic *ContainerEngine) createServiceContainer(ctx context.Context, name stri return ctr, nil } +func (ic *ContainerEngine) prepareAutomountImages(ctx context.Context, forContainer string, annotations map[string]string) ([]*specgen.ImageVolume, error) { + volMap := make(map[string]*specgen.ImageVolume) + + ctrAnnotation := define.KubeImageAutomountAnnotation + "/" + forContainer + + automount, ok := annotations[ctrAnnotation] + if !ok || automount == "" { + return nil, nil + } + + for _, imageName := range strings.Split(automount, ";") { + img, fullName, err := ic.Libpod.LibimageRuntime().LookupImage(imageName, nil) + if err != nil { + return nil, fmt.Errorf("image %s from container %s does not exist in local storage, cannot automount: %w", imageName, forContainer, err) + } + + logrus.Infof("Resolved image name %s to %s for automount into container %s", imageName, fullName, forContainer) + + inspect, err := img.Inspect(ctx, nil) + if err != nil { + return nil, fmt.Errorf("cannot inspect image %s to automount into container %s: %w", fullName, forContainer, err) + } + + volumes := inspect.Config.Volumes + + for path := range volumes { + if oldPath, ok := volMap[path]; ok && oldPath != nil { + logrus.Warnf("Multiple volume mounts to %q requested, overriding image %q with image %s", path, oldPath.Source, fullName) + } + + imgVol := new(specgen.ImageVolume) + imgVol.Source = fullName + imgVol.Destination = path + imgVol.ReadWrite = false + imgVol.SubPath = path + + volMap[path] = imgVol + } + } + + toReturn := make([]*specgen.ImageVolume, 0, len(volMap)) + for _, vol := range volMap { + toReturn = append(toReturn, vol) + } + + return toReturn, nil +} + func prepareVolumesFrom(forContainer, podName string, ctrNames, annotations map[string]string) ([]string, error) { annotationVolsFrom := define.VolumesFromAnnotation + "/" + forContainer @@ -829,6 +877,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY initCtrType = define.OneShotInitContainer } + automountImages, err := ic.prepareAutomountImages(ctx, initCtr.Name, annotations) + if err != nil { + return nil, nil, err + } + var volumesFrom []string if list, err := prepareVolumesFrom(initCtr.Name, podName, ctrNames, annotations); err != nil { return nil, nil, err @@ -857,6 +910,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, VolumesFrom: volumesFrom, + ImageVolumes: automountImages, UtsNSIsHost: p.UtsNs.IsHost(), } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) @@ -913,6 +967,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY labels[k] = v } + automountImages, err := ic.prepareAutomountImages(ctx, container.Name, annotations) + if err != nil { + return nil, nil, err + } + var volumesFrom []string if list, err := prepareVolumesFrom(container.Name, podName, ctrNames, annotations); err != nil { return nil, nil, err @@ -942,6 +1001,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, VolumesFrom: volumesFrom, + ImageVolumes: automountImages, UtsNSIsHost: p.UtsNs.IsHost(), } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index ec5cc10420..1328323ceb 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -142,6 +142,8 @@ type CtrSpecGenOptions struct { Volumes map[string]*KubeVolume // VolumesFrom for all containers VolumesFrom []string + // Image Volumes for this container + ImageVolumes []*specgen.ImageVolume // PodID of the parent pod PodID string // PodName of the parent pod @@ -223,6 +225,8 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener Driver: opts.LogDriver, } + s.ImageVolumes = opts.ImageVolumes + s.LogConfiguration.Options = make(map[string]string) for _, o := range opts.LogOptions { opt, val, hasVal := strings.Cut(o, "=") diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 07cfca1fb3..4dda259cd7 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -936,14 +936,19 @@ USER testuser`, CITEST_IMAGE) }) 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"} + podmanTest.AddImageToRWStore(ALPINE) - run1Cmd := append(ctrCommand, "/etc") + pathToCheck := "/sbin" + pathInCtr := "/mnt" + + ctrCommand := []string{"run", "--mount", fmt.Sprintf("type=image,source=%s,dst=%s,subpath=%s", ALPINE, pathInCtr, pathToCheck), ALPINE, "ls"} + + run1Cmd := append(ctrCommand, pathToCheck) run1 := podmanTest.Podman(run1Cmd) run1.WaitWithDefaultTimeout() Expect(run1).Should(ExitCleanly()) - run2Cmd := append(ctrCommand, "/mnt") + run2Cmd := append(ctrCommand, pathInCtr) run2 := podmanTest.Podman(run2Cmd) run2.WaitWithDefaultTimeout() Expect(run2).Should(ExitCleanly()) diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 174db05145..1441775748 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -981,3 +981,48 @@ _EOF run_podman pod rm -t 0 -f test_pod run_podman rmi -f userimage:latest $from_image } + +@test "podman play with automount volume" { + cat >$PODMAN_TMPDIR/Containerfile < $fname + + run_podman kube play --annotation "io.podman.annotations.kube.image.volumes.mount/testctr=automount_test" $fname + + run_podman run --rm automount_test ls /test1 + run_out_test1="$output" + run_podman exec test_pod-testctr ls /test1 + assert "$output" = "$run_out_test1" "matching ls run/exec volume path test1" + + run_podman run --rm automount_test ls /test2 + run_out_test2="$output" + run_podman exec test_pod-testctr ls /test2 + assert "$output" = "$run_out_test2" "matching ls run/exec volume path test2" + + run_podman rm -f -t 0 -a + run_podman rmi automount_test +}