diff --git a/pkg/resources/pod.go b/pkg/resources/pod.go index 24e9f41..5a52718 100644 --- a/pkg/resources/pod.go +++ b/pkg/resources/pod.go @@ -378,13 +378,25 @@ func applyPodOverrides(base, overrides corev1.PodSpec) corev1.PodSpec { return base } + // We diff overrides against an empty PodSpec to produce a patch containing only + // fields the user explicitly set (avoiding null fields from zero-value slices/maps + // that would otherwise wipe out base fields like Containers). + emptyJSON, err := json.Marshal(corev1.PodSpec{}) + if err != nil { + return base + } + patch, err := strategicpatch.CreateTwoWayMergePatch(emptyJSON, overridesJSON, corev1.PodSpec{}) + if err != nil { + return base + } + patchMeta, err := strategicpatch.NewPatchMetaFromStruct(&corev1.PodSpec{}) if err != nil { return base } merged, err := strategicpatch.StrategicMergePatchUsingLookupPatchMeta( - baseJSON, overridesJSON, patchMeta) + baseJSON, patch, patchMeta) if err != nil { return base } diff --git a/pkg/resources/pod_test.go b/pkg/resources/pod_test.go index 43a851d..fa4179a 100644 --- a/pkg/resources/pod_test.go +++ b/pkg/resources/pod_test.go @@ -310,6 +310,9 @@ func TestBuildPod_PodOverrides(t *testing.T) { if pod.Spec.NodeSelector["gpu"] != "true" { t.Errorf("expected nodeSelector gpu='true', got '%s'", pod.Spec.NodeSelector["gpu"]) } + if pod.Spec.Containers == nil { + t.Errorf("expected overrides not to touch container") + } } func TestBuildPod_CustomPort(t *testing.T) { diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 960f9b4..dde38d9 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -441,6 +441,49 @@ spec: _, _ = utils.Run(cmd) }) + It("should add volumes from podOverrides", func() { + notebookName := "e2e-pod-overrides" + + By("creating a MarimoNotebook with podOverrides volumes") + notebookYAML := fmt.Sprintf(` +apiVersion: marimo.io/v1alpha1 +kind: MarimoNotebook +metadata: + name: %s + namespace: %s +spec: + source: "https://github.com/marimo-team/marimo.git" + podOverrides: + volumes: + - name: ssh-pubkey + secret: + secretName: ssh-pubkey + defaultMode: 0600 +`, notebookName, testNamespace) + + yamlFile := filepath.Join("/tmp", notebookName+".yaml") + err := os.WriteFile(yamlFile, []byte(notebookYAML), 0644) + Expect(err).NotTo(HaveOccurred()) + + cmd := exec.Command("kubectl", "apply", "-f", yamlFile) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to create MarimoNotebook") + + By("verifying Pod has the overridden volume") + verifyVolumePresent := func(g Gomega) { + cmd := exec.Command("kubectl", "get", "pod", notebookName, "-n", testNamespace, + "-o", `jsonpath={.spec.volumes[?(@.name=="ssh-pubkey")].secret.secretName}`) + output, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(Equal("ssh-pubkey")) + } + Eventually(verifyVolumePresent, 2*time.Minute, time.Second).Should(Succeed()) + + By("cleaning up") + cmd = exec.Command("kubectl", "delete", "marimo", notebookName, "-n", testNamespace) + _, _ = utils.Run(cmd) + }) + It("should create sidecar containers with exposed ports", func() { notebookName := "e2e-sidecar"