Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ rules:
resources:
- jobs
verbs:
- delete
- deletecollection
- get
- list
Expand Down
91 changes: 21 additions & 70 deletions controllers/container_image/deployment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package container_image

import (
"context"
"reflect"

"go.mondoo.com/mondoo-operator/api/v1alpha2"
"go.mondoo.com/mondoo-operator/pkg/utils/k8s"
Expand All @@ -16,6 +15,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

var logger = ctrl.Log.WithName("k8s-images-scanning")
Expand Down Expand Up @@ -71,56 +71,31 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error {
return err
}

updated, err := n.syncConfigMap(ctx, clusterUid)
if err != nil {
if err := n.syncConfigMap(ctx, clusterUid); err != nil {
return err
}

// TODO: for CronJob we might consider triggering the CronJob now after the ConfigMap has been changed. It will make sense from the
// user perspective to want to run the jobs after you have updated the config.
if updated {
logger.Info(
"Inventory ConfigMap was just updated. The job will use the new config during the next scheduled run.",
"namespace", n.Mondoo.Namespace,
"name", CronJobName(n.Mondoo.Name))
}

// Reconcile private registry secrets (merges multiple secrets if needed)
privateRegistrySecretName, err := k8s.ReconcilePrivateRegistriesSecret(ctx, n.KubeClient, n.Mondoo)
if err != nil {
logger.Error(err, "Failed to reconcile private registry secrets")
return err
}

existing := &batchv1.CronJob{}
desired := CronJob(mondooClientImage, integrationMrn, clusterUid, privateRegistrySecretName, n.Mondoo, *n.MondooOperatorConfig)
if err := ctrl.SetControllerReference(n.Mondoo, desired, n.KubeClient.Scheme()); err != nil {
logger.Error(err, "Failed to set ControllerReference", "namespace", desired.Namespace, "name", desired.Name)
return err
}

created, err := k8s.CreateIfNotExist(ctx, n.KubeClient, existing, desired)
obj := &batchv1.CronJob{ObjectMeta: metav1.ObjectMeta{Name: desired.Name, Namespace: desired.Namespace}}
op, err := k8s.CreateOrUpdate(ctx, n.KubeClient, obj, n.Mondoo, logger, func() error {
k8s.UpdateCronJobFields(obj, desired)
return nil
})
if err != nil {
logger.Error(err, "Failed to create CronJob", "namespace", desired.Namespace, "name", desired.Name)
return err
}

if created {
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.Spec.ConcurrencyPolicy = desired.Spec.ConcurrencyPolicy
existing.SetOwnerReferences(desired.GetOwnerReferences())

// Remove completed/failed jobs because they won't be updated when the cronjob changes.
// Active jobs are preserved to avoid killing in-progress scans.
// When a CronJob is updated, remove completed Jobs so they don't linger with stale config
if op == controllerutil.OperationResultUpdated {
if err := k8s.DeleteCompletedJobs(ctx, n.KubeClient, n.Mondoo.Namespace, CronJobLabels(*n.Mondoo), logger); err != nil {
return err
}

if err := n.KubeClient.Update(ctx, existing); err != nil {
logger.Error(err, "Failed to update CronJob", "namespace", existing.Namespace, "name", existing.Name)
logger.Error(err, "Failed to clean up completed Jobs after CronJob update")
return err
}
}
Expand Down Expand Up @@ -154,53 +129,29 @@ func (n *DeploymentHandler) syncCronJob(ctx context.Context) error {
return nil
}

// syncConfigMap syncs the inventory ConfigMap. Returns a boolean indicating whether the ConfigMap has been updated. It
// can only be "true", if the ConfigMap existed before this reconcile cycle and the inventory was different from the
// desired state.
func (n *DeploymentHandler) syncConfigMap(ctx context.Context, clusterUid string) (bool, error) {
existing := &corev1.ConfigMap{}

func (n *DeploymentHandler) syncConfigMap(ctx context.Context, clusterUid string) error {
integrationMrn, err := k8s.TryGetIntegrationMrnForAuditConfig(ctx, n.KubeClient, *n.Mondoo)
if err != nil {
logger.Error(err, "failed to retrieve IntegrationMRN")
return false, err
return err
}

desired, err := ConfigMap(integrationMrn, clusterUid, *n.Mondoo, *n.MondooOperatorConfig)
if err != nil {
logger.Error(err, "failed to generate desired ConfigMap with inventory")
return false, err
}

if err := ctrl.SetControllerReference(n.Mondoo, desired, n.KubeClient.Scheme()); err != nil {
logger.Error(err, "Failed to set ControllerReference", "namespace", desired.Namespace, "name", desired.Name)
return false, err
}

created, err := k8s.CreateIfNotExist(ctx, n.KubeClient, existing, desired)
if err != nil {
logger.Error(err, "Failed to create inventory ConfigMap", "namespace", desired.Namespace, "name", desired.Name)
return false, err
return err
}

if created {
logger.Info("Created inventory ConfigMap", "namespace", desired.Namespace, "name", desired.Name)
return false, nil
obj := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: desired.Name, Namespace: desired.Namespace}}
if _, err := k8s.CreateOrUpdate(ctx, n.KubeClient, obj, n.Mondoo, logger, func() error {
obj.Labels = desired.Labels
obj.Data = desired.Data
return nil
}); err != nil {
return err
}

updated := false
if existing.Data["inventory"] != desired.Data["inventory"] ||
!reflect.DeepEqual(existing.GetOwnerReferences(), desired.GetOwnerReferences()) {
existing.Data["inventory"] = desired.Data["inventory"]
existing.SetOwnerReferences(desired.GetOwnerReferences())

if err := n.KubeClient.Update(ctx, existing); err != nil {
logger.Error(err, "Failed to update inventory ConfigMap", "namespace", existing.Namespace, "name", existing.Name)
return false, err
}
updated = true
}
return updated, nil
return nil
}

func (n *DeploymentHandler) getCronJobsForAuditConfig(ctx context.Context) ([]batchv1.CronJob, error) {
Expand Down
38 changes: 6 additions & 32 deletions controllers/container_image/deployment_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package container_image

import (
"context"
"fmt"
"testing"
"time"

Expand All @@ -16,7 +15,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

Expand Down Expand Up @@ -65,17 +63,13 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create() {
s.NoError(err)

expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// Set some fields that the kube client sets
expected.ResourceVersion = "1"

created := &batchv1.CronJob{}
created.Name = expected.Name
created.Namespace = expected.Namespace
s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created))

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_Create_CustomEnvVars() {
Expand All @@ -93,10 +87,6 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_CustomEnvVars() {
s.NoError(err)

expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// Set some fields that the kube client sets
expected.ResourceVersion = "1"

created := &batchv1.CronJob{}
created.Name = expected.Name
Expand All @@ -107,7 +97,7 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_CustomEnvVars() {
utils.SortEnvVars(expected.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Env)
utils.SortEnvVars(created.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Env)

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomImage() {
Expand All @@ -126,17 +116,13 @@ func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomImage() {
s.NoError(err)

expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// Set some fields that the kube client sets
expected.ResourceVersion = "1"

created := &batchv1.CronJob{}
created.Name = expected.Name
created.Namespace = expected.Namespace
s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created))

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_CreateWithCustomSchedule() {
Expand Down Expand Up @@ -192,17 +178,13 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_PrivateRegistriesSecret()
s.NoError(err)

expected := CronJob(image, "", test.KubeSystemNamespaceUid, s.auditConfig.Spec.Scanner.PrivateRegistriesPullSecretRef.Name, &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// Set some fields that the kube client sets
expected.ResourceVersion = "1"

created := &batchv1.CronJob{}
created.Name = expected.Name
created.Namespace = expected.Namespace
s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created))

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_Create_MultiplePrivateRegistriesSecrets() {
Expand Down Expand Up @@ -292,17 +274,13 @@ func (s *DeploymentHandlerSuite) TestReconcile_Create_ConsoleIntegration() {
s.NoError(err)

expected := CronJob(image, integrationMrn, test.KubeSystemNamespaceUid, "", &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// Set some fields that the kube client sets
expected.ResourceVersion = "1"

created := &batchv1.CronJob{}
created.Name = expected.Name
created.Namespace = expected.Namespace
s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created))

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_Update() {
Expand All @@ -324,17 +302,13 @@ func (s *DeploymentHandlerSuite) TestReconcile_Update() {
s.True(result.IsZero())

expected := CronJob(image, "", test.KubeSystemNamespaceUid, "", &s.auditConfig, mondoov1alpha2.MondooOperatorConfig{})
s.NoError(ctrl.SetControllerReference(&s.auditConfig, expected, d.KubeClient.Scheme()))

// The second node has an updated cron job so resource version is +1
expected.ResourceVersion = fmt.Sprintf("%d", 2)

created := &batchv1.CronJob{}
created.Name = expected.Name
created.Namespace = expected.Namespace
s.NoError(d.KubeClient.Get(s.ctx, client.ObjectKeyFromObject(created), created))

s.Equal(expected, created)
s.Equal(expected.Spec, created.Spec)
}

func (s *DeploymentHandlerSuite) TestReconcile_K8sContainerImageScanningStatus() {
Expand Down
4 changes: 3 additions & 1 deletion controllers/container_image/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ func CronJob(image, integrationMrn, clusterUid, privateRegistrySecretName string
MountPath: "/tmp",
},
},
Env: envVars,
Env: envVars,
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
},
},
ServiceAccountName: m.Spec.Scanner.ServiceAccountName,
Expand Down
Loading
Loading