diff --git a/test/e2e/advanced/job_reload_test.go b/test/e2e/advanced/job_reload_test.go index 907125d..b003475 100644 --- a/test/e2e/advanced/job_reload_test.go +++ b/test/e2e/advanced/job_reload_test.go @@ -1,6 +1,9 @@ package advanced import ( + "fmt" + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -9,21 +12,27 @@ import ( var _ = Describe("Job Workload Recreation Tests", func() { var ( - jobName string - configMapName string - secretName string + jobName string + configMapName string + secretName string + spcName string + vaultSecretPath string ) BeforeEach(func() { jobName = utils.RandName("job") configMapName = utils.RandName("cm") secretName = utils.RandName("secret") + spcName = utils.RandName("spc") + vaultSecretPath = fmt.Sprintf("secret/%s", utils.RandName("vault")) }) AfterEach(func() { _ = utils.DeleteJob(ctx, kubeClient, testNamespace, jobName) _ = utils.DeleteConfigMap(ctx, kubeClient, testNamespace, configMapName) _ = utils.DeleteSecret(ctx, kubeClient, testNamespace, secretName) + _ = utils.DeleteSecretProviderClass(ctx, csiClient, testNamespace, spcName) + _ = utils.DeleteVaultSecret(ctx, kubeClient, restConfig, vaultSecretPath) }) Context("Job with ConfigMap reference", func() { @@ -40,8 +49,8 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(err).NotTo(HaveOccurred()) originalUID := string(job.UID) - By("Waiting for Job to exist") - err = utils.WaitForJobExists(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) Expect(err).NotTo(HaveOccurred()) By("Updating the ConfigMap") @@ -69,8 +78,8 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(err).NotTo(HaveOccurred()) originalUID := string(job.UID) - By("Waiting for Job to exist") - err = utils.WaitForJobExists(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) Expect(err).NotTo(HaveOccurred()) By("Updating the Secret") @@ -99,8 +108,8 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(err).NotTo(HaveOccurred()) originalUID := string(job.UID) - By("Waiting for Job to exist") - err = utils.WaitForJobExists(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) Expect(err).NotTo(HaveOccurred()) By("Updating the ConfigMap") @@ -129,8 +138,8 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(err).NotTo(HaveOccurred()) originalUID := string(job.UID) - By("Waiting for Job to exist") - err = utils.WaitForJobExists(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) Expect(err).NotTo(HaveOccurred()) By("Updating the ConfigMap") @@ -160,8 +169,8 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(err).NotTo(HaveOccurred()) originalUID := string(job.UID) - By("Waiting for Job to exist") - err = utils.WaitForJobExists(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) Expect(err).NotTo(HaveOccurred()) By("Updating the Secret") @@ -175,4 +184,70 @@ var _ = Describe("Job Workload Recreation Tests", func() { Expect(recreated).To(BeTrue(), "Job with valueFrom.secretKeyRef should be recreated when Secret changes") }) }) + + Context("Job with SecretProviderClass reference", Label("csi"), func() { + BeforeEach(func() { + // Skip if CSI driver not installed + if !utils.IsCSIDriverInstalled(ctx, csiClient) { + Skip("CSI secrets store driver not installed - skipping CSI test") + } + // Skip if Vault CSI provider not installed + if !utils.IsVaultProviderInstalled(ctx, kubeClient) { + Skip("Vault CSI provider not installed - skipping CSI test") + } + }) + + It("should recreate Job when Vault secret changes", func() { + By("Creating a secret in Vault") + err := utils.CreateVaultSecret( + ctx, kubeClient, restConfig, vaultSecretPath, map[string]string{"api_key": "initial-value-v1"}) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a SecretProviderClass pointing to Vault secret") + _, err = utils.CreateSecretProviderClassWithSecret( + ctx, csiClient, testNamespace, spcName, + vaultSecretPath, "api_key", + ) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a Job with CSI volume and SPC reload annotation") + job, err := utils.CreateJob(ctx, kubeClient, testNamespace, jobName, + utils.WithJobCSIVolume(spcName), + utils.WithJobAnnotations(utils.BuildSecretProviderClassReloadAnnotation(spcName))) + Expect(err).NotTo(HaveOccurred()) + originalUID := string(job.UID) + + By("Waiting for Job to be ready") + err = utils.WaitForJobReady(ctx, kubeClient, testNamespace, jobName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Finding the SPCPS created by CSI driver") + spcpsName, err := utils.FindSPCPSForSPC( + ctx, csiClient, testNamespace, spcName, utils.DeploymentReady, + ) + Expect(err).NotTo(HaveOccurred()) + GinkgoWriter.Printf("Found SPCPS: %s\n", spcpsName) + + By("Getting initial SPCPS version") + initialVersion, err := utils.GetSPCPSVersion(ctx, csiClient, testNamespace, spcpsName) + Expect(err).NotTo(HaveOccurred()) + GinkgoWriter.Printf("Initial SPCPS version: %s\n", initialVersion) + + By("Updating the Vault secret") + err = utils.UpdateVaultSecret( + ctx, kubeClient, restConfig, vaultSecretPath, map[string]string{"api_key": "updated-value-v2"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for CSI driver to sync the new secret version") + err = utils.WaitForSPCPSVersionChange(ctx, csiClient, testNamespace, spcpsName, initialVersion, 10*time.Second) + Expect(err).NotTo(HaveOccurred()) + GinkgoWriter.Println("CSI driver synced new secret version") + + By("Waiting for Job to be recreated (new UID)") + _, recreated, err := utils.WaitForJobRecreated(ctx, kubeClient, testNamespace, jobName, originalUID, + utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(recreated).To(BeTrue(), "Job should be recreated with new UID when Vault secret changes") + }) + }) }) diff --git a/test/e2e/advanced/pod_annotations_test.go b/test/e2e/advanced/pod_annotations_test.go deleted file mode 100644 index 310a9df..0000000 --- a/test/e2e/advanced/pod_annotations_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package advanced - -import ( - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/stakater/Reloader/test/e2e/utils" -) - -var _ = Describe("Pod Template Annotations Tests", func() { - var ( - deploymentName string - configMapName string - secretName string - ) - - BeforeEach(func() { - deploymentName = utils.RandName("deploy") - configMapName = utils.RandName("cm") - secretName = utils.RandName("secret") - }) - - AfterEach(func() { - _ = utils.DeleteDeployment(ctx, kubeClient, testNamespace, deploymentName) - _ = utils.DeleteConfigMap(ctx, kubeClient, testNamespace, configMapName) - _ = utils.DeleteSecret(ctx, kubeClient, testNamespace, secretName) - }) - - Context("Annotations on pod template metadata only", func() { - It("should reload when using annotation on pod template metadata (not deployment metadata)", func() { - By("Creating a ConfigMap") - _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, - map[string]string{"POD_CONFIG": "initial"}, nil) - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Deployment with annotation ONLY on pod template") - _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, - utils.WithConfigMapEnvFrom(configMapName), - utils.WithPodTemplateAnnotations(utils.BuildConfigMapReloadAnnotation(configMapName)), - // Note: No WithAnnotations - annotation only on pod template - ) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be ready") - err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) - Expect(err).NotTo(HaveOccurred()) - - By("Updating the ConfigMap") - err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, map[string]string{"POD_CONFIG": "updated"}) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be reloaded") - reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, - utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) - Expect(err).NotTo(HaveOccurred()) - Expect(reloaded).To(BeTrue(), "Deployment should reload when annotation is on pod template metadata") - }) - }) - - Context("Annotations on both deployment and pod template metadata", func() { - It("should reload when annotations are on both deployment and pod template", func() { - By("Creating a ConfigMap") - _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, - map[string]string{"BOTH_CONFIG": "initial"}, nil) - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Deployment with annotation on BOTH deployment and pod template") - _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, - utils.WithConfigMapEnvFrom(configMapName), - utils.WithAnnotations(utils.BuildConfigMapReloadAnnotation(configMapName)), - utils.WithPodTemplateAnnotations(utils.BuildConfigMapReloadAnnotation(configMapName)), - ) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be ready") - err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) - Expect(err).NotTo(HaveOccurred()) - - By("Updating the ConfigMap") - err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, map[string]string{"BOTH_CONFIG": "updated"}) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be reloaded") - reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, - utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) - Expect(err).NotTo(HaveOccurred()) - Expect(reloaded).To(BeTrue(), "Deployment should reload when annotations are on both locations") - }) - }) - - Context("auto=true annotation on pod template", func() { - It("should reload when auto annotation is on pod template metadata", func() { - By("Creating a ConfigMap") - _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, - map[string]string{"AUTO_POD_CONFIG": "initial"}, nil) - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Deployment with auto=true annotation on pod template") - _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, - utils.WithConfigMapEnvFrom(configMapName), - utils.WithPodTemplateAnnotations(utils.BuildAutoTrueAnnotation()), - ) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be ready") - err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) - Expect(err).NotTo(HaveOccurred()) - - By("Updating the ConfigMap") - err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, map[string]string{"AUTO_POD_CONFIG": "updated"}) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be reloaded") - reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, - utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) - Expect(err).NotTo(HaveOccurred()) - Expect(reloaded).To(BeTrue(), "Deployment with auto=true on pod template should reload") - }) - }) - - Context("Secret annotation on pod template", func() { - It("should reload when secret reload annotation is on pod template", func() { - By("Creating a Secret") - _, err := utils.CreateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, - map[string]string{"POD_SECRET": "initial"}, nil) - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Deployment with secret reload annotation on pod template") - _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, - utils.WithSecretEnvFrom(secretName), - utils.WithPodTemplateAnnotations(utils.BuildSecretReloadAnnotation(secretName)), - ) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be ready") - err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) - Expect(err).NotTo(HaveOccurred()) - - By("Updating the Secret") - err = utils.UpdateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, map[string]string{"POD_SECRET": "updated"}) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be reloaded") - reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, - utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) - Expect(err).NotTo(HaveOccurred()) - Expect(reloaded).To(BeTrue(), "Deployment should reload when secret annotation is on pod template") - }) - }) - - Context("Mismatched annotations (different resources)", func() { - It("should NOT reload when pod template has ConfigMap annotation but we update Secret", func() { - By("Creating both ConfigMap and Secret") - _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, - map[string]string{"CONFIG": "value"}, nil) - Expect(err).NotTo(HaveOccurred()) - - _, err = utils.CreateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, - map[string]string{"SECRET": "initial"}, nil) - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Deployment with ConfigMap annotation on pod template but using Secret") - _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, - utils.WithSecretEnvFrom(secretName), - utils.WithPodTemplateAnnotations(utils.BuildConfigMapReloadAnnotation(configMapName)), - ) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for Deployment to be ready") - err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) - Expect(err).NotTo(HaveOccurred()) - - By("Updating the Secret (not the ConfigMap)") - err = utils.UpdateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, map[string]string{"SECRET": "updated"}) - Expect(err).NotTo(HaveOccurred()) - - By("Verifying Deployment was NOT reloaded (negative test)") - time.Sleep(utils.NegativeTestWait) - reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, - utils.AnnotationLastReloadedFrom, utils.ShortTimeout) - Expect(err).NotTo(HaveOccurred()) - Expect(reloaded).To(BeFalse(), "Deployment should NOT reload when we update different resource than annotated") - }) - }) -}) diff --git a/test/e2e/annotations/annotations_suite_test.go b/test/e2e/annotations/annotations_suite_test.go index a4e9154..f4559ce 100644 --- a/test/e2e/annotations/annotations_suite_test.go +++ b/test/e2e/annotations/annotations_suite_test.go @@ -21,6 +21,7 @@ var ( ctx context.Context cancel context.CancelFunc testEnv *utils.TestEnvironment + registry *utils.AdapterRegistry ) func TestAnnotations(t *testing.T) { @@ -40,6 +41,23 @@ var _ = BeforeSuite(func() { restConfig = testEnv.RestConfig testNamespace = testEnv.Namespace + registry = utils.NewAdapterRegistry(kubeClient) + + // Register optional adapters if CRDs are installed + if utils.IsArgoRolloutsInstalled(ctx, testEnv.RolloutsClient) { + GinkgoWriter.Println("Argo Rollouts detected, registering ArgoRolloutAdapter") + registry.RegisterAdapter(utils.NewArgoRolloutAdapter(testEnv.RolloutsClient)) + } else { + GinkgoWriter.Println("Argo Rollouts not detected, skipping ArgoRolloutAdapter registration") + } + + if utils.HasDeploymentConfigSupport(testEnv.DiscoveryClient) && testEnv.OpenShiftClient != nil { + GinkgoWriter.Println("OpenShift detected, registering DeploymentConfigAdapter") + registry.RegisterAdapter(utils.NewDeploymentConfigAdapter(testEnv.OpenShiftClient)) + } else { + GinkgoWriter.Println("OpenShift not detected, skipping DeploymentConfigAdapter registration") + } + deployValues := map[string]string{ "reloader.reloadStrategy": "annotations", "reloader.watchGlobally": "false", // Only watch own namespace to prevent cross-talk between test suites diff --git a/test/e2e/annotations/exclude_test.go b/test/e2e/annotations/exclude_test.go index 4486717..644f8aa 100644 --- a/test/e2e/annotations/exclude_test.go +++ b/test/e2e/annotations/exclude_test.go @@ -17,6 +17,7 @@ var _ = Describe("Exclude Annotation Tests", func() { configMapName2 string secretName string secretName2 string + workloadName string ) BeforeEach(func() { @@ -25,6 +26,7 @@ var _ = Describe("Exclude Annotation Tests", func() { configMapName2 = utils.RandName("cm2") secretName = utils.RandName("secret") secretName2 = utils.RandName("secret2") + workloadName = utils.RandName("workload") }) AfterEach(func() { @@ -185,6 +187,58 @@ var _ = Describe("Exclude Annotation Tests", func() { }) }) + Context("Exclude annotation on pod template", func() { + DescribeTable("should NOT reload when exclude annotation is on pod template only", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating two ConfigMaps") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + _, err = utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName2, + map[string]string{"key2": "initial2"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with auto=true and exclude annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + ConfigMapName: configMapName, + UseConfigMapEnvFrom: true, + PodTemplateAnnotations: utils.MergeAnnotations( + utils.BuildAutoTrueAnnotation(), + utils.BuildConfigMapExcludeAnnotation(configMapName), + ), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the excluded ConfigMap") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying workload was NOT reloaded (excluded ConfigMap)") + time.Sleep(utils.NegativeTestWait) + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ShortTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeFalse(), "%s should NOT reload with exclude on pod template", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + }) + Context("SecretProviderClass exclude annotation", Label("csi"), func() { var ( spcName string diff --git a/test/e2e/annotations/pause_period_test.go b/test/e2e/annotations/pause_period_test.go index 1e31de9..00c68d8 100644 --- a/test/e2e/annotations/pause_period_test.go +++ b/test/e2e/annotations/pause_period_test.go @@ -97,5 +97,43 @@ var _ = Describe("Pause Period Tests", func() { Expect(err).NotTo(HaveOccurred()) Expect(paused).To(BeFalse(), "Deployment should NOT have paused-at annotation without pause-period") }) + + It("should pause Deployment when pause-period annotation is on pod template", func() { + By("Creating a ConfigMap") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a Deployment with pause-period annotation on pod template ONLY") + _, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName, + utils.WithConfigMapEnvFrom(configMapName), + utils.WithPodTemplateAnnotations(utils.MergeAnnotations( + utils.BuildConfigMapReloadAnnotation(configMapName), + utils.BuildPausePeriodAnnotation("10s"), + )), + ) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Deployment to be ready") + err = utils.WaitForDeploymentReady(ctx, kubeClient, testNamespace, deploymentName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the ConfigMap data") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Deployment to be reloaded") + reloaded, err := utils.WaitForDeploymentReloaded(ctx, kubeClient, testNamespace, deploymentName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "Deployment should have been reloaded") + + By("Verifying Deployment has paused-at annotation") + paused, err := utils.WaitForDeploymentPaused(ctx, kubeClient, testNamespace, deploymentName, + utils.AnnotationDeploymentPausedAt, utils.ShortTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(paused).To(BeTrue(), "Deployment should have paused-at annotation with pause-period on pod template") + }) }) }) diff --git a/test/e2e/annotations/search_match_test.go b/test/e2e/annotations/search_match_test.go index bc794e0..30676d4 100644 --- a/test/e2e/annotations/search_match_test.go +++ b/test/e2e/annotations/search_match_test.go @@ -1,6 +1,7 @@ package annotations import ( + "fmt" "time" . "github.com/onsi/ginkgo/v2" @@ -13,11 +14,13 @@ var _ = Describe("Search and Match Annotation Tests", func() { var ( deploymentName string configMapName string + workloadName string ) BeforeEach(func() { deploymentName = utils.RandName("deploy") configMapName = utils.RandName("cm") + workloadName = utils.RandName("workload") }) AfterEach(func() { @@ -163,4 +166,49 @@ var _ = Describe("Search and Match Annotation Tests", func() { Expect(reloaded2).To(BeFalse(), "Deployment without search annotation should NOT reload") }) }) + + Context("with search annotation on pod template", func() { + DescribeTable("should reload when search annotation is on pod template only", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a ConfigMap with match annotation") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, + utils.BuildMatchAnnotation()) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with search annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + ConfigMapName: configMapName, + UseConfigMapEnvFrom: true, + PodTemplateAnnotations: utils.BuildSearchAnnotation(), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the ConfigMap") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with search annotation on pod template", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + }) }) diff --git a/test/e2e/core/workloads_test.go b/test/e2e/core/workloads_test.go index 4966ac4..14c2b0a 100644 --- a/test/e2e/core/workloads_test.go +++ b/test/e2e/core/workloads_test.go @@ -194,7 +194,9 @@ var _ = Describe("Workload Reload Tests", func() { Expect(reloaded).To(BeTrue(), "%s should have been reloaded when Vault secret changed", workloadType) }, Entry("Deployment", Label("csi"), utils.WorkloadDeployment), Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), - Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet)) + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) // Auto=true annotation tests DescribeTable("should reload with auto=true annotation when ConfigMap changes", @@ -377,7 +379,9 @@ var _ = Describe("Workload Reload Tests", func() { Expect(reloaded).To(BeFalse(), "%s should NOT reload when only SPCPS labels change", workloadType) }, Entry("Deployment", Label("csi"), utils.WorkloadDeployment), Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), - Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet)) + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) // CronJob special handling - triggers a Job instead of annotation Context("CronJob (special handling)", func() { @@ -803,6 +807,366 @@ var _ = Describe("Workload Reload Tests", func() { Expect(reloaded).To(BeFalse(), "Deployment with auto=false should NOT have been reloaded") }) }) + + // ============================================================ + // POD TEMPLATE ANNOTATION TESTS + // These tests verify that annotations placed on the pod template + // (spec.template.metadata.annotations) work the same as annotations + // placed on the workload metadata (metadata.annotations). + // ============================================================ + Context("Pod Template Annotations", func() { + DescribeTable("should reload when ConfigMap annotation is on pod template only", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a ConfigMap") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with ConfigMap annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + ConfigMapName: configMapName, + UseConfigMapEnvFrom: true, + PodTemplateAnnotations: utils.BuildConfigMapReloadAnnotation(configMapName), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the ConfigMap") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with pod template annotation", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should reload when Secret annotation is on pod template only", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a Secret") + _, err := utils.CreateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, + map[string]string{"password": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with Secret annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + SecretName: secretName, + UseSecretEnvFrom: true, + PodTemplateAnnotations: utils.BuildSecretReloadAnnotation(secretName), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the Secret") + err = utils.UpdateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, + map[string]string{"password": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with pod template annotation", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should reload when auto=true annotation is on pod template only", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a ConfigMap") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with auto=true annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + ConfigMapName: configMapName, + UseConfigMapEnvFrom: true, + PodTemplateAnnotations: utils.BuildAutoTrueAnnotation(), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the ConfigMap") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s with auto=true on pod template should reload", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should reload when SecretProviderClass annotation is on pod template only", + func(workloadType utils.WorkloadType) { + if !utils.IsCSIDriverInstalled(ctx, csiClient) { + Skip("CSI secrets store driver not installed") + } + if !utils.IsVaultProviderInstalled(ctx, kubeClient) { + Skip("Vault CSI provider not installed") + } + + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a secret in Vault") + err := utils.CreateVaultSecret(ctx, kubeClient, restConfig, vaultSecretPath, + map[string]string{"api_key": "initial-value-v1"}) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a SecretProviderClass pointing to Vault secret") + _, err = utils.CreateSecretProviderClassWithSecret(ctx, csiClient, testNamespace, spcName, + vaultSecretPath, "api_key") + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with SPC annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + SPCName: spcName, + UseCSIVolume: true, + PodTemplateAnnotations: utils.BuildSecretProviderClassReloadAnnotation(spcName), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Finding the SPCPS created by CSI driver") + spcpsName, err := utils.FindSPCPSForDeployment(ctx, csiClient, kubeClient, testNamespace, + workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Getting initial SPCPS version") + initialVersion, err := utils.GetSPCPSVersion(ctx, csiClient, testNamespace, spcpsName) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the Vault secret") + err = utils.UpdateVaultSecret(ctx, kubeClient, restConfig, vaultSecretPath, + map[string]string{"api_key": "updated-value-v2"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for CSI driver to sync the new secret version") + err = utils.WaitForSPCPSVersionChange(ctx, csiClient, testNamespace, spcpsName, + initialVersion, 10*time.Second) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with SPC annotation on pod template", workloadType) + }, + Entry("Deployment", Label("csi"), utils.WorkloadDeployment), + Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should reload when secretproviderclass auto annotation is on pod template only", + func(workloadType utils.WorkloadType) { + if !utils.IsCSIDriverInstalled(ctx, csiClient) { + Skip("CSI secrets store driver not installed") + } + if !utils.IsVaultProviderInstalled(ctx, kubeClient) { + Skip("Vault CSI provider not installed") + } + + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a secret in Vault") + err := utils.CreateVaultSecret(ctx, kubeClient, restConfig, vaultSecretPath, + map[string]string{"api_key": "initial-value-v1"}) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a SecretProviderClass pointing to Vault secret") + _, err = utils.CreateSecretProviderClassWithSecret(ctx, csiClient, testNamespace, spcName, + vaultSecretPath, "api_key") + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with SPC auto annotation on pod template ONLY") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + SPCName: spcName, + UseCSIVolume: true, + PodTemplateAnnotations: utils.BuildSecretProviderClassAutoAnnotation(), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Finding the SPCPS created by CSI driver") + spcpsName, err := utils.FindSPCPSForDeployment(ctx, csiClient, kubeClient, testNamespace, + workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Getting initial SPCPS version") + initialVersion, err := utils.GetSPCPSVersion(ctx, csiClient, testNamespace, spcpsName) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the Vault secret") + err = utils.UpdateVaultSecret(ctx, kubeClient, restConfig, vaultSecretPath, + map[string]string{"api_key": "updated-value-v2"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for CSI driver to sync the new secret version") + err = utils.WaitForSPCPSVersionChange(ctx, csiClient, testNamespace, spcpsName, + initialVersion, 10*time.Second) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with SPC auto on pod template", workloadType) + }, + Entry("Deployment", Label("csi"), utils.WorkloadDeployment), + Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should reload when annotations are on both workload and pod template", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a ConfigMap") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with annotations on BOTH workload metadata and pod template") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + ConfigMapName: configMapName, + UseConfigMapEnvFrom: true, + Annotations: utils.BuildConfigMapReloadAnnotation(configMapName), + PodTemplateAnnotations: utils.BuildConfigMapReloadAnnotation(configMapName), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the ConfigMap") + err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for workload to be reloaded") + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ReloadTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeTrue(), "%s should reload with annotations on both locations", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + + DescribeTable("should NOT reload when pod template has ConfigMap annotation but Secret is updated", + func(workloadType utils.WorkloadType) { + adapter := registry.Get(workloadType) + if adapter == nil { + Skip(fmt.Sprintf("%s adapter not available (CRD not installed)", workloadType)) + } + + By("Creating a ConfigMap and Secret") + _, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName, + map[string]string{"key": "value"}, nil) + Expect(err).NotTo(HaveOccurred()) + + _, err = utils.CreateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, + map[string]string{"password": "initial"}, nil) + Expect(err).NotTo(HaveOccurred()) + + By("Creating workload with ConfigMap annotation on pod template but using Secret") + err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{ + SecretName: secretName, + UseSecretEnvFrom: true, + PodTemplateAnnotations: utils.BuildConfigMapReloadAnnotation(configMapName), + }) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = adapter.Delete(ctx, testNamespace, workloadName) }) + + By("Waiting for workload to be ready") + err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the Secret (not the ConfigMap)") + err = utils.UpdateSecretFromStrings(ctx, kubeClient, testNamespace, secretName, + map[string]string{"password": "updated"}) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying workload was NOT reloaded (negative test)") + time.Sleep(utils.NegativeTestWait) + reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName, + utils.AnnotationLastReloadedFrom, utils.ShortTimeout) + Expect(err).NotTo(HaveOccurred()) + Expect(reloaded).To(BeFalse(), "%s should NOT reload when updating different resource than annotated", workloadType) + }, + Entry("Deployment", utils.WorkloadDeployment), + Entry("DaemonSet", utils.WorkloadDaemonSet), + Entry("StatefulSet", utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("openshift"), utils.WorkloadDeploymentConfig)) + }) }) // ============================================================ @@ -998,7 +1362,9 @@ var _ = Describe("Workload Reload Tests", func() { Expect(found).To(BeTrue(), "%s should have STAKATER_ env var after Vault secret change", workloadType) }, Entry("Deployment", Label("csi"), utils.WorkloadDeployment), Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), - Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet)) + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) // Negative tests for env var strategy DescribeTable("should NOT add STAKATER_ env var when only ConfigMap labels change", @@ -1148,7 +1514,9 @@ var _ = Describe("Workload Reload Tests", func() { workloadType) }, Entry("Deployment", Label("csi"), utils.WorkloadDeployment), Entry("DaemonSet", Label("csi"), utils.WorkloadDaemonSet), - Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet)) + Entry("StatefulSet", Label("csi"), utils.WorkloadStatefulSet), + Entry("ArgoRollout", Label("csi", "argo"), utils.WorkloadArgoRollout), + Entry("DeploymentConfig", Label("csi", "openshift"), utils.WorkloadDeploymentConfig)) // CSI auto annotation with EnvVar strategy and real Vault It("should add STAKATER_ env var with secretproviderclass auto annotation", Label("csi"), func() { diff --git a/test/e2e/utils/csi.go b/test/e2e/utils/csi.go index e5c1a04..654e3d7 100644 --- a/test/e2e/utils/csi.go +++ b/test/e2e/utils/csi.go @@ -245,9 +245,10 @@ func execInVaultPod(ctx context.Context, kubeClient kubernetes.Interface, restCo return fmt.Errorf("creating executor: %w", err) } - var stderr bytes.Buffer + var stdout, stderr bytes.Buffer err = exec.StreamWithContext( ctx, remotecommand.StreamOptions{ + Stdout: &stdout, Stderr: &stderr, }, ) diff --git a/test/e2e/utils/podspec.go b/test/e2e/utils/podspec.go index df9011f..21c44a5 100644 --- a/test/e2e/utils/podspec.go +++ b/test/e2e/utils/podspec.go @@ -193,9 +193,21 @@ func AddInitContainerWithVolumes(spec *corev1.PodSpec, cmName, secretName string spec.InitContainers = append(spec.InitContainers, init) } -// ApplyWorkloadConfig applies all WorkloadConfig settings to a PodSpec. -// This single function replaces all the duplicate buildXxxOptions functions. -func ApplyWorkloadConfig(spec *corev1.PodSpec, cfg WorkloadConfig) { +// ApplyWorkloadConfig applies all WorkloadConfig settings to a PodTemplateSpec. +// This includes both pod template annotations and pod spec configuration. +func ApplyWorkloadConfig(template *corev1.PodTemplateSpec, cfg WorkloadConfig) { + // Apply pod template annotations + if len(cfg.PodTemplateAnnotations) > 0 { + if template.Annotations == nil { + template.Annotations = make(map[string]string) + } + for k, v := range cfg.PodTemplateAnnotations { + template.Annotations[k] = v + } + } + + // Apply pod spec configuration + spec := &template.Spec if cfg.UseConfigMapEnvFrom && cfg.ConfigMapName != "" { AddEnvFromSource(spec, 0, cfg.ConfigMapName, false) } diff --git a/test/e2e/utils/resources.go b/test/e2e/utils/resources.go index 1963537..8543f23 100644 --- a/test/e2e/utils/resources.go +++ b/test/e2e/utils/resources.go @@ -897,6 +897,35 @@ func WithJobSecretKeyRef(secretName, key, envVarName string) JobOption { } } +// WithJobCSIVolume adds a CSI volume referencing a SecretProviderClass to a Job. +func WithJobCSIVolume(spcName string) JobOption { + return func(j *batchv1.Job) { + volumeName := csiVolumeName(spcName) + mountPath := csiMountPath(spcName) + + j.Spec.Template.Spec.Volumes = append(j.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + Driver: CSIDriverName, + ReadOnly: ptr.To(true), + VolumeAttributes: map[string]string{ + "secretProviderClass": spcName, + }, + }, + }, + }) + j.Spec.Template.Spec.Containers[0].VolumeMounts = append( + j.Spec.Template.Spec.Containers[0].VolumeMounts, + corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + ReadOnly: true, + }, + ) + } +} + // baseJobResource creates a base Job template. func baseJobResource(namespace, name string) *batchv1.Job { labels := map[string]string{"app": name} diff --git a/test/e2e/utils/wait.go b/test/e2e/utils/wait.go index e0b54d0..0fc70ec 100644 --- a/test/e2e/utils/wait.go +++ b/test/e2e/utils/wait.go @@ -288,6 +288,26 @@ func WaitForJobExists(ctx context.Context, client kubernetes.Interface, namespac ) } +// WaitForJobReady waits for a Job to have at least one active or succeeded pod. +// This ensures the Job has actually started running before proceeding. +func WaitForJobReady(ctx context.Context, client kubernetes.Interface, namespace, name string, timeout time.Duration) error { + return wait.PollUntilContextTimeout( + ctx, DefaultInterval, timeout, true, func(ctx context.Context) (bool, error) { + job, err := client.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + // Job is ready if it has at least one active or succeeded pod + if job.Status.Active > 0 || job.Status.Succeeded > 0 { + return true, nil + } + + return false, nil + }, + ) +} + // GetPodLogs retrieves logs from pods matching the given label selector. func GetPodLogs(ctx context.Context, client kubernetes.Interface, namespace, labelSelector string) (string, error) { pods, err := client.CoreV1().Pods(namespace).List( diff --git a/test/e2e/utils/workload_adapter.go b/test/e2e/utils/workload_adapter.go index 0b42836..50daa9c 100644 --- a/test/e2e/utils/workload_adapter.go +++ b/test/e2e/utils/workload_adapter.go @@ -33,7 +33,8 @@ type WorkloadConfig struct { ConfigMapName string SecretName string SPCName string - Annotations map[string]string + Annotations map[string]string // Annotations for workload metadata (e.g., Deployment.metadata.annotations) + PodTemplateAnnotations map[string]string // Annotations for pod template metadata (e.g., Deployment.spec.template.metadata.annotations) UseConfigMapEnvFrom bool UseSecretEnvFrom bool UseConfigMapVolume bool diff --git a/test/e2e/utils/workload_argo.go b/test/e2e/utils/workload_argo.go index 32ee45e..5ec6f1e 100644 --- a/test/e2e/utils/workload_argo.go +++ b/test/e2e/utils/workload_argo.go @@ -116,7 +116,7 @@ func buildRolloutOptions(cfg WorkloadConfig) []RolloutOption { r.Annotations[k] = v } } - ApplyWorkloadConfig(&r.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(&r.Spec.Template, cfg) }, } } diff --git a/test/e2e/utils/workload_cronjob.go b/test/e2e/utils/workload_cronjob.go index 6b74bfd..0f52d7c 100644 --- a/test/e2e/utils/workload_cronjob.go +++ b/test/e2e/utils/workload_cronjob.go @@ -79,7 +79,8 @@ func buildCronJobOptions(cfg WorkloadConfig) []CronJobOption { cj.Annotations[k] = v } } - ApplyWorkloadConfig(&cj.Spec.JobTemplate.Spec.Template.Spec, cfg) + // CronJob has nested JobTemplate + ApplyWorkloadConfig(&cj.Spec.JobTemplate.Spec.Template, cfg) }, } } diff --git a/test/e2e/utils/workload_daemonset.go b/test/e2e/utils/workload_daemonset.go index 12e54ab..93c6e64 100644 --- a/test/e2e/utils/workload_daemonset.go +++ b/test/e2e/utils/workload_daemonset.go @@ -73,7 +73,7 @@ func buildDaemonSetOptions(cfg WorkloadConfig) []DaemonSetOption { ds.Annotations[k] = v } } - ApplyWorkloadConfig(&ds.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(&ds.Spec.Template, cfg) }, } } diff --git a/test/e2e/utils/workload_deployment.go b/test/e2e/utils/workload_deployment.go index 3a28231..b0cbfb1 100644 --- a/test/e2e/utils/workload_deployment.go +++ b/test/e2e/utils/workload_deployment.go @@ -73,7 +73,7 @@ func buildDeploymentOptions(cfg WorkloadConfig) []DeploymentOption { d.Annotations[k] = v } } - ApplyWorkloadConfig(&d.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(&d.Spec.Template, cfg) }, } } diff --git a/test/e2e/utils/workload_job.go b/test/e2e/utils/workload_job.go index 15ecaa7..c83d24f 100644 --- a/test/e2e/utils/workload_job.go +++ b/test/e2e/utils/workload_job.go @@ -93,7 +93,7 @@ func buildJobOptions(cfg WorkloadConfig) []JobOption { job.Annotations[k] = v } } - ApplyWorkloadConfig(&job.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(&job.Spec.Template, cfg) }, } } diff --git a/test/e2e/utils/workload_openshift.go b/test/e2e/utils/workload_openshift.go index 9fd6866..3e89a40 100644 --- a/test/e2e/utils/workload_openshift.go +++ b/test/e2e/utils/workload_openshift.go @@ -112,7 +112,7 @@ func buildDeploymentConfigOptions(cfg WorkloadConfig) []DCOption { } } if dc.Spec.Template != nil { - ApplyWorkloadConfig(&dc.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(dc.Spec.Template, cfg) } }, } diff --git a/test/e2e/utils/workload_statefulset.go b/test/e2e/utils/workload_statefulset.go index 5696128..c8dadbe 100644 --- a/test/e2e/utils/workload_statefulset.go +++ b/test/e2e/utils/workload_statefulset.go @@ -73,7 +73,7 @@ func buildStatefulSetOptions(cfg WorkloadConfig) []StatefulSetOption { sts.Annotations[k] = v } } - ApplyWorkloadConfig(&sts.Spec.Template.Spec, cfg) + ApplyWorkloadConfig(&sts.Spec.Template, cfg) }, } }