feat: Add missing tests for edge cases and all other workload types for pod annotations

This commit is contained in:
TheiLLeniumStudios
2026-01-14 14:30:53 +01:00
parent b35016ce1e
commit 4f254826e2
19 changed files with 694 additions and 216 deletions

View File

@@ -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")
})
})
})

View File

@@ -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")
})
})
})

View File

@@ -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

View File

@@ -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

View File

@@ -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")
})
})
})

View File

@@ -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))
})
})

View File

@@ -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() {

View File

@@ -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,
},
)

View File

@@ -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)
}

View File

@@ -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}

View File

@@ -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(

View File

@@ -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

View File

@@ -116,7 +116,7 @@ func buildRolloutOptions(cfg WorkloadConfig) []RolloutOption {
r.Annotations[k] = v
}
}
ApplyWorkloadConfig(&r.Spec.Template.Spec, cfg)
ApplyWorkloadConfig(&r.Spec.Template, cfg)
},
}
}

View File

@@ -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)
},
}
}

View File

@@ -73,7 +73,7 @@ func buildDaemonSetOptions(cfg WorkloadConfig) []DaemonSetOption {
ds.Annotations[k] = v
}
}
ApplyWorkloadConfig(&ds.Spec.Template.Spec, cfg)
ApplyWorkloadConfig(&ds.Spec.Template, cfg)
},
}
}

View File

@@ -73,7 +73,7 @@ func buildDeploymentOptions(cfg WorkloadConfig) []DeploymentOption {
d.Annotations[k] = v
}
}
ApplyWorkloadConfig(&d.Spec.Template.Spec, cfg)
ApplyWorkloadConfig(&d.Spec.Template, cfg)
},
}
}

View File

@@ -93,7 +93,7 @@ func buildJobOptions(cfg WorkloadConfig) []JobOption {
job.Annotations[k] = v
}
}
ApplyWorkloadConfig(&job.Spec.Template.Spec, cfg)
ApplyWorkloadConfig(&job.Spec.Template, cfg)
},
}
}

View File

@@ -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)
}
},
}

View File

@@ -73,7 +73,7 @@ func buildStatefulSetOptions(cfg WorkloadConfig) []StatefulSetOption {
sts.Annotations[k] = v
}
}
ApplyWorkloadConfig(&sts.Spec.Template.Spec, cfg)
ApplyWorkloadConfig(&sts.Spec.Template, cfg)
},
}
}