From 34748fbc4da114d10f83a1039770ef0a2671b79c Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Tue, 22 Apr 2025 13:09:14 -0400 Subject: [PATCH] Correct `generate kube` on containers userns annotation The `podman generate kube` command on containers follows a different codepath from pods. Pods store a lot of pod-level configuration - including user namespace information - in annotations, so it can be restored by `play kube`. Generating for a container does not do the same thing, because we don't have a pod. However, per-container generation was still generating a nearly identical user namespace annotation to a pod. Example: In Pod: io.podman.annotations.userns: auto:size=40 Not in Pod: io.podman.annotations.userns/awesomegreider: auto:size=2048 The second annotation seems like it should apply a user namespace config to the generated Kubernetes pod. Instead, it's just adding an annotation to the awesomegreider container, that says said container has a user namespace, when it does not in fact have a user namespace configured because it is now in a pod. After this PR, both containers in and out of pods generate identical annotations (the In Pod version, missing container name) and as such should generate pods with appropriately configured user namespaces. I also added some conflict detection to refuse to generate if you try to generate YAML containing two containers with conflicting user namespace configuration. Fixes #25896 Signed-off-by: Matt Heon --- libpod/kube.go | 12 +++++++++++- test/e2e/generate_kube_test.go | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/libpod/kube.go b/libpod/kube.go index 76433e67f4..d8e08d7273 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -765,7 +765,17 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getServic if !podmanOnly && define.IsReservedAnnotation(k) { continue } - kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v + // Certain annotations should be applied to the whole pod. + // For others, add container name as a suffix. + // For annotations such as this, error if already set. + if k == define.UserNsAnnotation { + if oldV, ok := kubeAnnotations[k]; ok && oldV != v { + return nil, fmt.Errorf("two or more containers have differing user namespace configuration, cannot place in same Kubernetes pod: %w", define.ErrInvalidArg) + } + kubeAnnotations[k] = v + } else { + kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v + } } // Convert auto-update labels into kube annotations diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 4af76aa480..b654a938d5 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -1025,6 +1025,27 @@ var _ = Describe("Podman kube generate", func() { Expect(kube).Should(ExitCleanly()) }) + It("multiple containers with same user namespace configuration", func() { + name1 := "c1" + name2 := "c2" + _ = podmanTest.PodmanExitCleanly("run", "--userns", "auto:size=30", "-dt", "--name", name1, ALPINE, "top") + _ = podmanTest.PodmanExitCleanly("run", "--userns", "auto:size=30", "-dt", "--name", name2, ALPINE, "top") + + gen := podmanTest.PodmanExitCleanly("kube", "generate", name1, name2) + Expect(gen.OutputToString()).To(ContainSubstring("io.podman.annotations.userns: auto:size=10")) + }) + + It("multiple containers with differing user namespace configuration", func() { + name1 := "c1" + name2 := "c2" + _ = podmanTest.PodmanExitCleanly("run", "--userns", "auto:size=30", "-dt", "--name", name1, ALPINE, "top") + _ = podmanTest.PodmanExitCleanly("run", "--userns", "auto:size=40", "-dt", "--name", name2, ALPINE, "top") + + gen := podmanTest.Podman([]string{"kube", "generate", name1, name2}) + gen.WaitWithDefaultTimeout() + Expect(gen).Should(ExitWithError(125, "two or more containers have differing user namespace configuration, cannot place in same Kubernetes pod: invalid argument")) + }) + It("with containers in pods should fail", func() { pod1 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod1", "--name", "top1", CITEST_IMAGE, "top"}) pod1.WaitWithDefaultTimeout()