summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Heon <mheon@redhat.com>2024-04-17 08:23:38 -0400
committerMatt Heon <mheon@redhat.com>2024-04-25 14:12:27 -0400
commit30e2c923d6f0b7e9fc46e353288804e3fbbbad67 (patch)
tree4b3d6ea9c1bccbac156a185a293c2b41739244b6
parent693ae0ebc60d648b870eb25c6d2fb155c280da61 (diff)
Add the ability to automount images as volumes via play
Effectively, this is an ability to take an image already pulled to the system, and automatically mount it into one or more containers defined in Kubernetes YAML accepted by `podman play`. Requirements: - The image must already exist in storage. - 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. As we're using a nonstandard annotation, this is Podman only, any Kubernetes install will just ignore this. Underneath, this compiles down to an image volume (`podman run --mount type=image,...`) with subpaths to specify what bits we want to mount into the container. Signed-off-by: Matt Heon <mheon@redhat.com>
-rw-r--r--docs/source/markdown/podman-kube-play.1.md.in11
-rw-r--r--libpod/define/annotations.go3
-rw-r--r--pkg/domain/infra/abi/play.go60
-rw-r--r--pkg/specgen/generate/kube/kube.go4
-rw-r--r--test/e2e/run_volume_test.go11
-rw-r--r--test/system/700-play.bats45
6 files changed, 131 insertions, 3 deletions
diff --git a/docs/source/markdown/podman-kube-play.1.md.in b/docs/source/markdown/podman-kube-play.1.md.in
index ea2ebf4a0..21a929a13 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 a9d4031ae..ac1956f56 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 f1202dfce..fa7ffb8ea 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 ec5cc1042..1328323ce 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 07cfca1fb..4dda259cd 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 174db0514..144177574 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 <<EOF
+FROM $IMAGE
+RUN mkdir /test1 /test2
+RUN touch /test1/a /test1/b /test1/c
+RUN touch /test2/asdf /test2/ejgre /test2/lteghe
+VOLUME /test1
+VOLUME /test2
+EOF
+
+ run_podman build -t automount_test -f $PODMAN_TMPDIR/Containerfile
+
+ fname="/tmp/play_kube_wait_$(random_string 6).yaml"
+ echo "
+apiVersion: v1
+kind: Pod
+metadata:
+ labels:
+ app: test
+ name: test_pod
+spec:
+ restartPolicy: Never
+ containers:
+ - name: testctr
+ image: $IMAGE
+ command:
+ - top
+" > $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
+}