From bf8c252019cc0304414665b345fa034b4c13a797 Mon Sep 17 00:00:00 2001 From: Christian Zunker <827818+czunker@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:24:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Expose=20CronJob=20schedula=20via?= =?UTF-8?q?=20config=20(#938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is now possible to change the CronJob schedule for the different types of CronJobs via the `MondooAuditConfig`. Fixes #888 Signed-off-by: Christian Zunker --- api/v1alpha2/mondooauditconfig_types.go | 6 ++ .../k8s.mondoo.com_mondooauditconfigs.yaml | 14 +++++ .../container_image/deployment_handler.go | 1 + .../deployment_handler_test.go | 57 ++++++++++++++---- controllers/container_image/resources.go | 11 ++++ controllers/k8s_scan/deployment_handler.go | 1 + .../k8s_scan/deployment_handler_test.go | 58 +++++++++++++++++++ controllers/k8s_scan/resources.go | 11 ++++ controllers/nodes/deployment_handler.go | 1 + controllers/nodes/deployment_handler_test.go | 54 +++++++++++++++++ controllers/nodes/resources.go | 17 +++++- docs/user-manual.md | 19 ++++++ go.mod | 1 + go.sum | 2 + pkg/utils/k8s/equality.go | 1 + pkg/utils/k8s/equality_test.go | 9 +++ 16 files changed, 248 insertions(+), 15 deletions(-) diff --git a/api/v1alpha2/mondooauditconfig_types.go b/api/v1alpha2/mondooauditconfig_types.go index 6241a1bfc..925512107 100644 --- a/api/v1alpha2/mondooauditconfig_types.go +++ b/api/v1alpha2/mondooauditconfig_types.go @@ -86,11 +86,15 @@ type KubernetesResources struct { // DEPRECATED: ContainerImageScanning determines whether container images are being scanned. The current implementation // runs a separate job once every 24h that scans the container images running in the cluster. ContainerImageScanning bool `json:"containerImageScanning,omitempty"` + // Specify a custom crontab schedule for the Kubernetes resource scanning job. If not specified, the default schedule is used. + Schedule string `json:"schedule,omitempty"` } type Nodes struct { Enable bool `json:"enable,omitempty"` Resources corev1.ResourceRequirements `json:"resources,omitempty"` + // Specify a custom crontab schedule for the node scanning job. If not specified, the default schedule is used. + Schedule string `json:"schedule,omitempty"` } type Admission struct { @@ -118,6 +122,8 @@ type Admission struct { type Containers struct { Enable bool `json:"enable,omitempty"` Resources corev1.ResourceRequirements `json:"resources,omitempty"` + // Specify a custom crontab schedule for the container image scanning job. If not specified, the default schedule is used. + Schedule string `json:"schedule,omitempty"` } type Image struct { diff --git a/config/crd/bases/k8s.mondoo.com_mondooauditconfigs.yaml b/config/crd/bases/k8s.mondoo.com_mondooauditconfigs.yaml index a69a04e94..4e8e0b40a 100644 --- a/config/crd/bases/k8s.mondoo.com_mondooauditconfigs.yaml +++ b/config/crd/bases/k8s.mondoo.com_mondooauditconfigs.yaml @@ -143,6 +143,11 @@ spec: Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + schedule: + description: Specify a custom crontab schedule for the container + image scanning job. If not specified, the default schedule is + used. + type: string type: object filtering: properties: @@ -175,6 +180,11 @@ spec: type: boolean enable: type: boolean + schedule: + description: Specify a custom crontab schedule for the Kubernetes + resource scanning job. If not specified, the default schedule + is used. + type: string type: object mondooCredsSecretRef: description: Config is an example field of MondooAuditConfig. Edit @@ -252,6 +262,10 @@ spec: Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + schedule: + description: Specify a custom crontab schedule for the node scanning + job. If not specified, the default schedule is used. + type: string type: object scanner: description: Scanner defines the settings for the Mondoo scanner that diff --git a/controllers/container_image/deployment_handler.go b/controllers/container_image/deployment_handler.go index d0f219d89..8ec0c56a7 100644 --- a/controllers/container_image/deployment_handler.go +++ b/controllers/container_image/deployment_handler.go @@ -125,6 +125,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { logger.Info("Created CronJob", "namespace", desired.Namespace, "name", desired.Name) } else if !k8s.AreCronJobsEqual(*existing, *desired) { existing.Spec.JobTemplate = desired.Spec.JobTemplate + existing.Spec.Schedule = desired.Spec.Schedule existing.SetOwnerReferences(desired.GetOwnerReferences()) if err := n.KubeClient.Update(ctx, existing); err != nil { diff --git a/controllers/container_image/deployment_handler_test.go b/controllers/container_image/deployment_handler_test.go index 79a80d7eb..97c0f09b3 100644 --- a/controllers/container_image/deployment_handler_test.go +++ b/controllers/container_image/deployment_handler_test.go @@ -59,9 +59,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create() { s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) @@ -92,9 +89,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomImage() { s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - image, err := s.containerImageResolver.CnspecImage("ubuntu", "22.04", false) s.NoError(err) @@ -115,6 +109,51 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomImage() { s.Equal(expected, created) } +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomSchedule() { + d := s.createDeploymentHandler() + + customSchedule := "0 0 * * *" + s.auditConfig.Spec.Containers.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) + + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.Equal(created.Spec.Schedule, customSchedule) +} + +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomScheduleFail() { + d := s.createDeploymentHandler() + + customSchedule := "this is not valid" + s.auditConfig.Spec.Containers.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", s.auditConfig, mondoov1alpha2.MondooOperatorConfig{}) + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.NotEqual(created.Spec.Schedule, customSchedule) +} + func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecret() { d := s.createDeploymentHandler() @@ -135,9 +174,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecret() s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) @@ -177,9 +213,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_ConsoleIntegration() { s.NoError(err) s.True(result.IsZero()) - nodes := &corev1.NodeList{} - s.NoError(d.KubeClient.List(s.ctx, nodes)) - image, err := s.containerImageResolver.CnspecImage("", "", false) s.NoError(err) diff --git a/controllers/container_image/resources.go b/controllers/container_image/resources.go index 3f361187c..59356072e 100644 --- a/controllers/container_image/resources.go +++ b/controllers/container_image/resources.go @@ -8,6 +8,8 @@ import ( "strings" "time" + // That's the mod k8s relies on https://github.com/kubernetes/kubernetes/blob/master/go.mod#L63 + "github.com/robfig/cron/v3" "go.mondoo.com/cnquery/v9/providers-sdk/v1/inventory" "go.mondoo.com/mondoo-operator/api/v1alpha2" "go.mondoo.com/mondoo-operator/pkg/constants" @@ -49,6 +51,15 @@ func CronJob(image, integrationMrn, clusterUid, privateImageScanningSecretName s // We want to start the cron job one minute after it was enabled. cronStart := time.Now().Add(1 * time.Minute) cronTab := fmt.Sprintf("%d %d * * *", cronStart.Minute(), cronStart.Hour()) + if m.Spec.Containers.Schedule != "" { + _, err := cron.ParseStandard(m.Spec.Containers.Schedule) + if err != nil { + logger.Error(err, "invalid cron schedule specified in MondooAuditConfig Spec.Containers.Schedule; using default") + } else { + logger.Info("using cron custom schedule", "crontab", m.Spec.Containers.Schedule) + cronTab = m.Spec.Containers.Schedule + } + } cronjob := &batchv1.CronJob{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/k8s_scan/deployment_handler.go b/controllers/k8s_scan/deployment_handler.go index 36e1a4b5a..1f7ab637b 100644 --- a/controllers/k8s_scan/deployment_handler.go +++ b/controllers/k8s_scan/deployment_handler.go @@ -93,6 +93,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { logger.Info("Created CronJob", "namespace", desired.Namespace, "name", desired.Name) } else if !k8s.AreCronJobsEqual(*existing, *desired) { existing.Spec.JobTemplate = desired.Spec.JobTemplate + existing.Spec.Schedule = desired.Spec.Schedule existing.SetOwnerReferences(desired.GetOwnerReferences()) if err := n.KubeClient.Update(ctx, existing); err != nil { diff --git a/controllers/k8s_scan/deployment_handler_test.go b/controllers/k8s_scan/deployment_handler_test.go index 238e1bb28..866358048 100644 --- a/controllers/k8s_scan/deployment_handler_test.go +++ b/controllers/k8s_scan/deployment_handler_test.go @@ -319,6 +319,64 @@ func (s *DeploymentHandlerSuite) TestReconcile_Disable() { s.Equal(0, len(cronJobs.Items)) } +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomSchedule() { + d := s.createDeploymentHandler() + + scanApiUrl := scanapi.ScanApiServiceUrl(*d.Mondoo) + s.scanApiStoreMock.EXPECT().Add(&scan_api_store.ScanApiStoreAddOpts{ + Url: scanApiUrl, + Token: "token", + }).Times(1) + + customSchedule := "0 0 * * *" + s.auditConfig.Spec.KubernetesResources.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, "", test.KubeSystemNamespaceUid, s.auditConfig) + + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.Equal(created.Spec.Schedule, customSchedule) +} + +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomScheduleFail() { + d := s.createDeploymentHandler() + + scanApiUrl := scanapi.ScanApiServiceUrl(*d.Mondoo) + s.scanApiStoreMock.EXPECT().Add(&scan_api_store.ScanApiStoreAddOpts{ + Url: scanApiUrl, + Token: "token", + }).Times(1) + + customSchedule := "this is not valid" + s.auditConfig.Spec.KubernetesResources.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, "", test.KubeSystemNamespaceUid, s.auditConfig) + + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.NotEqual(created.Spec.Schedule, customSchedule) +} + func (s *DeploymentHandlerSuite) createDeploymentHandler() DeploymentHandler { return DeploymentHandler{ KubeClient: s.fakeClientBuilder.Build(), diff --git a/controllers/k8s_scan/resources.go b/controllers/k8s_scan/resources.go index 5829b3a71..7782ff904 100644 --- a/controllers/k8s_scan/resources.go +++ b/controllers/k8s_scan/resources.go @@ -8,6 +8,8 @@ import ( "strings" "time" + // That's the mod k8s relies on https://github.com/kubernetes/kubernetes/blob/master/go.mod#L63 + "github.com/robfig/cron/v3" "go.mondoo.com/mondoo-operator/api/v1alpha2" "go.mondoo.com/mondoo-operator/controllers/scanapi" "go.mondoo.com/mondoo-operator/pkg/feature_flags" @@ -24,6 +26,15 @@ func CronJob(image, integrationMrn, clusterUid string, m v1alpha2.MondooAuditCon ls := CronJobLabels(m) cronTab := fmt.Sprintf("%d * * * *", time.Now().Add(1*time.Minute).Minute()) + if m.Spec.KubernetesResources.Schedule != "" { + _, err := cron.ParseStandard(m.Spec.KubernetesResources.Schedule) + if err != nil { + logger.Error(err, "invalid cron schedule specified in MondooAuditConfig Spec.KubernetesResources.Schedule; using default") + } else { + logger.Info("using cron custom schedule", "crontab", m.Spec.KubernetesResources.Schedule) + cronTab = m.Spec.KubernetesResources.Schedule + } + } scanApiUrl := scanapi.ScanApiServiceUrl(m) containerArgs := []string{ diff --git a/controllers/nodes/deployment_handler.go b/controllers/nodes/deployment_handler.go index 61cfa9fb4..53e84bbd0 100644 --- a/controllers/nodes/deployment_handler.go +++ b/controllers/nodes/deployment_handler.go @@ -103,6 +103,7 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error { if !k8s.AreCronJobsEqual(*existing, *desired) { existing.Spec.JobTemplate = desired.Spec.JobTemplate + existing.Spec.Schedule = desired.Spec.Schedule existing.SetOwnerReferences(desired.GetOwnerReferences()) if err := n.KubeClient.Update(ctx, existing); err != nil { diff --git a/controllers/nodes/deployment_handler_test.go b/controllers/nodes/deployment_handler_test.go index daa2ef260..6560ff1fc 100644 --- a/controllers/nodes/deployment_handler_test.go +++ b/controllers/nodes/deployment_handler_test.go @@ -457,6 +457,60 @@ func (s *DeploymentHandlerSuite) TestReconcile_DisableNodeScanning() { s.Equal(0, len(cronJobs.Items)) } +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomSchedule() { + s.seedNodes() + d := s.createDeploymentHandler() + + customSchedule := "0 0 * * *" + s.auditConfig.Spec.Nodes.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + nodes := &corev1.NodeList{} + s.NoError(d.KubeClient.List(s.ctx, nodes)) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, nodes.Items[0], s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) + + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.Equal(created.Spec.Schedule, customSchedule) +} + +func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomScheduleFail() { + s.seedNodes() + d := s.createDeploymentHandler() + + customSchedule := "this is not valid" + s.auditConfig.Spec.Nodes.Schedule = customSchedule + + result, err := d.Reconcile(s.ctx) + s.NoError(err) + s.True(result.IsZero()) + + nodes := &corev1.NodeList{} + s.NoError(d.KubeClient.List(s.ctx, nodes)) + + image, err := s.containerImageResolver.CnspecImage("", "", false) + s.NoError(err) + + expected := CronJob(image, nodes.Items[0], s.auditConfig, false, v1alpha2.MondooOperatorConfig{}) + + created := &batchv1.CronJob{} + created.Name = expected.Name + created.Namespace = expected.Namespace + s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created)) + + s.NotEqual(created.Spec.Schedule, customSchedule) +} + func (s *DeploymentHandlerSuite) createDeploymentHandler() DeploymentHandler { return DeploymentHandler{ KubeClient: s.fakeClientBuilder.Build(), diff --git a/controllers/nodes/resources.go b/controllers/nodes/resources.go index c2d475ea9..b3d41c2f6 100644 --- a/controllers/nodes/resources.go +++ b/controllers/nodes/resources.go @@ -16,6 +16,8 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/yaml" + // That's the mod k8s relies on https://github.com/kubernetes/kubernetes/blob/master/go.mod#L63 + "github.com/robfig/cron/v3" "go.mondoo.com/cnquery/v9/providers-sdk/v1/inventory" "go.mondoo.com/mondoo-operator/api/v1alpha2" "go.mondoo.com/mondoo-operator/controllers/scanapi" @@ -38,6 +40,15 @@ func CronJob(image string, node corev1.Node, m v1alpha2.MondooAuditConfig, isOpe ls := CronJobLabels(m) cronTab := fmt.Sprintf("%d * * * *", time.Now().Add(1*time.Minute).Minute()) + if m.Spec.Nodes.Schedule != "" { + _, err := cron.ParseStandard(m.Spec.Nodes.Schedule) + if err != nil { + logger.Error(err, "invalid cron schedule specified in MondooAuditConfig Spec.Nodes.Schedule; using default") + } else { + logger.Info("using cron custom schedule", "crontab", m.Spec.Nodes.Schedule) + cronTab = m.Spec.Nodes.Schedule + } + } unsetHostPath := corev1.HostPathUnset name := "cnspec" @@ -193,7 +204,7 @@ func CronJob(image string, node corev1.Node, m v1alpha2.MondooAuditConfig, isOpe func GarbageCollectCronJob(image, clusterUid string, m v1alpha2.MondooAuditConfig) *batchv1.CronJob { ls := CronJobLabels(m) - cronTab := fmt.Sprintf("%d */2 * * *", time.Now().Add(1*time.Minute).Minute()) + cronTab := fmt.Sprintf("%d */12 * * *", time.Now().Add(1*time.Minute).Minute()) scanApiUrl := scanapi.ScanApiServiceUrl(m) containerArgs := []string{ "garbage-collect", @@ -202,8 +213,8 @@ func GarbageCollectCronJob(image, clusterUid string, m v1alpha2.MondooAuditConfi // The job runs hourly and we need to make sure that the previous one is killed before the new one is started so we don't stack them. "--timeout", "55", - // Cleanup any resources more than 2 hours old - "--filter-older-than", "2h", + // Cleanup any resources more than 48 hours old + "--filter-older-than", "48h", "--labels", "k8s.mondoo.com/kind=node", } diff --git a/docs/user-manual.md b/docs/user-manual.md index 4532f7337..2f65cc5ca 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -449,6 +449,25 @@ subjects: After some seconds, you should see that the operator picked up the new `MondooAuditConfig` and starts creating objects. +## Adjust the scan interval + +You can adjust the interval for scans triggered via a CronJob. +Edit the `MondooAuditConfig` to adjust the interval: +``` +kubectl -n mondoo-operator edit mondooauditconfigs.k8s.mondoo.com mondoo-client +``` + +``` + kubernetesResources: + enable: true + schedule: 41 * * * * +``` + +You can adjust the schedule for the following components: +- Kubernetes Resources Scanning +- Container Image Scanning +- Node Scanning + ## Uninstalling the Mondoo operator Before uninstalling the Mondoo operator, be sure to delete all `MondooAuditConfig` and `MondooOperatorConfig` objects. You can find any in your cluster by running: diff --git a/go.mod b/go.mod index ae0c43694..afbebe055 100644 --- a/go.mod +++ b/go.mod @@ -335,6 +335,7 @@ require ( github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/robfig/cron/v3 v3.0.1 github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 diff --git a/go.sum b/go.sum index f4a770ac5..b6ab72951 100644 --- a/go.sum +++ b/go.sum @@ -793,6 +793,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= diff --git a/pkg/utils/k8s/equality.go b/pkg/utils/k8s/equality.go index e0d2620a8..e83a13560 100644 --- a/pkg/utils/k8s/equality.go +++ b/pkg/utils/k8s/equality.go @@ -80,6 +80,7 @@ func AreCronJobsEqual(a, b batchv1.CronJob) bool { AreSecurityContextsEqual(aPodSpec.Containers[0].SecurityContext, bPodSpec.Containers[0].SecurityContext) && reflect.DeepEqual(aPodSpec.Volumes, bPodSpec.Volumes) && reflect.DeepEqual(a.Spec.SuccessfulJobsHistoryLimit, b.Spec.SuccessfulJobsHistoryLimit) && + a.Spec.Schedule == b.Spec.Schedule && reflect.DeepEqual(a.Spec.FailedJobsHistoryLimit, b.Spec.FailedJobsHistoryLimit) && reflect.DeepEqual(a.GetOwnerReferences(), b.GetOwnerReferences()) } diff --git a/pkg/utils/k8s/equality_test.go b/pkg/utils/k8s/equality_test.go index 12b0d8538..a71f60426 100644 --- a/pkg/utils/k8s/equality_test.go +++ b/pkg/utils/k8s/equality_test.go @@ -578,6 +578,15 @@ func TestAreCronJobsEqual(t *testing.T) { }, shouldBeEqual: false, }, + { + name: "should not be equal when schedules differ", + createB: func(a batchv1.CronJob) batchv1.CronJob { + b := *a.DeepCopy() + b.Spec.Schedule = "1 * * * *" + return b + }, + shouldBeEqual: false, + }, } for _, test := range tests {