diff --git a/CHANGELOG.md b/CHANGELOG.md index 3293cfa5a..3b6f45dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,16 @@ Versioning](https://semver.org/spec/v2.0.0.html). corrected the descriptions, and also added a new `rationale` field to `ComplianceCheckResult` objects. +- Make Compliance Operator to apply all the related remediations for + one ComplianceCheckResult at once, this helps users who use manual + remediation, this feature will look for all the related remediations + for a ComplianceCheckResult when one remediation is applied. For ex. + we have `cp4-cis-kubelet-evictio...-inodesfree`, `cp4-cis-kubelet-evictio...-inodesfree-1`, + remediations, when a user applies either one of them, we will apply + all the other remediations associate with the rule. + [OCPBUGS-4338]https://issues.redhat.com/browse/OCPBUGS-4338 + + ### Internal Changes - The Compliance Operator now marks a `ScanSettingBinding` that uses a diff --git a/pkg/controller/complianceremediation/complianceremediation_controller.go b/pkg/controller/complianceremediation/complianceremediation_controller.go index 28bd99ad6..5b5884257 100644 --- a/pkg/controller/complianceremediation/complianceremediation_controller.go +++ b/pkg/controller/complianceremediation/complianceremediation_controller.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "strings" "time" @@ -39,6 +40,7 @@ var log = logf.Log.WithName(ctrlName) const ( remediationNameAnnotationKey = "remediation/" defaultDependencyRequeueTime = time.Second * 20 + remediationSuffixRegex = "-[0-9]+" // matches - ) func (r *ReconcileComplianceRemediation) SetupWithManager(mgr ctrl.Manager) error { @@ -172,6 +174,7 @@ func (r *ReconcileComplianceRemediation) Reconcile(ctx context.Context, request return reconcile.Result{}, valueReqErr } } + //if no UnmetDependencies, UnsetValue, ValueRequired if !(remediationInstance.HasUnmetDependencies() || remediationInstance.HasAnnotation(compv1alpha1.RemediationUnsetValueAnnotation) || remediationInstance.HasAnnotation(compv1alpha1.RemediationValueRequiredAnnotation)) { reconcileErr = r.reconcileRemediation(remediationInstance, reqLogger) @@ -237,9 +240,18 @@ func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1al } else if kerrors.IsNotFound(err) { if instance.Spec.Apply { instance.AddOwnershipLabels(obj) - return r.createRemediation(obj, objectLogger) + // Going through remediation list, to make sure all the related + // remediations objects are set to apply + err := r.setRemediations(instance, objectLogger, true) + if err != nil { + return fmt.Errorf("failed to set related remediations to apply: %w", err) + } + err = r.createRemediation(obj, objectLogger) + if err != nil { + return fmt.Errorf("failed to create remediation: %w", err) + } + return nil } - objectLogger.Info("The object wasn't found, so no action is needed to unapply it") return nil } else if err != nil { @@ -247,12 +259,52 @@ func (r *ReconcileComplianceRemediation) reconcileRemediation(instance *compv1al } if instance.Spec.Apply { + err = r.setRemediations(instance, objectLogger, true) + if err != nil { + return fmt.Errorf("failed to set related remediations to apply: %w", err) + } return r.patchRemediation(obj, objectLogger) } - + err = r.setRemediations(instance, objectLogger, false) + if err != nil { + return fmt.Errorf("failed to set related remediations to unapply: %w", err) + } return r.deleteRemediation(obj, found, objectLogger) } +// find all the other releated remediation and set the apply to true or false +func (r *ReconcileComplianceRemediation) setRemediations(instance *compv1alpha1.ComplianceRemediation, logger logr.Logger, apply bool) error { + remediations := &compv1alpha1.ComplianceRemediationList{} + err := r.Client.List(context.TODO(), remediations, client.InNamespace(instance.GetNamespace())) + if err != nil { + return fmt.Errorf("couldn't list remediations: %w", err) + } + + for _, remediation := range remediations.Items { + remName := remediation.Name + if remName == instance.Name { + continue + } + + // filter the name of the remediation, to take out the "-number" suffix + reg := regexp.MustCompile(remediationSuffixRegex) + filteredName := reg.ReplaceAllString(remName, "${1}") + filterdInstanceName := reg.ReplaceAllString(instance.Name, "${1}") + + if filteredName == filterdInstanceName { + if remediation.Spec.Apply != apply { + remediation.Spec.Apply = apply + err = r.Client.Update(context.TODO(), &remediation) + if err != nil { + return fmt.Errorf("couldn't update remediation: %w", err) + } + } + } + } + + return nil +} + func (r *ReconcileComplianceRemediation) createRemediation(remObj *unstructured.Unstructured, logger logr.Logger) error { logger.Info("Remediation will be created") compv1alpha1.AddRemediationAnnotation(remObj) diff --git a/pkg/controller/complianceremediation/complianceremediation_controller_test.go b/pkg/controller/complianceremediation/complianceremediation_controller_test.go index e6ea478cf..474dbc847 100644 --- a/pkg/controller/complianceremediation/complianceremediation_controller_test.go +++ b/pkg/controller/complianceremediation/complianceremediation_controller_test.go @@ -99,7 +99,9 @@ var _ = Describe("Testing complianceremediation controller", func() { }, }, } + remediationinstance.Spec.Type = compv1alpha1.ConfigurationRemediation + scanInstance = &compv1alpha1.ComplianceScan{ ObjectMeta: metav1.ObjectMeta{ Name: "myScan", @@ -162,6 +164,7 @@ var _ = Describe("Testing complianceremediation controller", func() { err := reconciler.reconcileRemediation(remediationinstance, logger) Expect(err).To(BeNil()) + By("the remediation should be applied") foundCM := &corev1.ConfigMap{} err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: "my-cm", Namespace: "test-ns"}, foundCM) @@ -171,6 +174,98 @@ var _ = Describe("Testing complianceremediation controller", func() { }) }) + Context("Apply all the related remediation", func() { + BeforeEach(func() { + + cm2 := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cm-2", + Namespace: "test-ns", + }, + Data: map[string]string{ + "key": "val", + }, + } + + unstructuredCM2, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm2) + Expect(err).ToNot(HaveOccurred()) + + remediationinstance2 := &compv1alpha1.ComplianceRemediation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testRem-2", + Labels: testRemLabels, + Annotations: testRemAnnotations, + }, + Spec: compv1alpha1.ComplianceRemediationSpec{ + Current: compv1alpha1.ComplianceRemediationPayload{ + Object: nil, + }, + }, + } + remediationinstance2.Spec.Type = compv1alpha1.ConfigurationRemediation + remediationinstance2.Spec.Current.Object = &unstructured.Unstructured{ + Object: unstructuredCM2, + } + + err = reconciler.Client.Create(context.TODO(), remediationinstance2) + Expect(err).NotTo(HaveOccurred()) + + cm := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cm", + Namespace: "test-ns", + }, + Data: map[string]string{ + "key": "val", + }, + } + + unstructuredCM, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm) + Expect(err).ToNot(HaveOccurred()) + remediationinstance.Spec.Current.Object = &unstructured.Unstructured{ + Object: unstructuredCM, + } + err = reconciler.Client.Update(context.TODO(), remediationinstance) + Expect(err).NotTo(HaveOccurred()) + + }) + + It("should reconcile the current remediation", func() { + By("running a reconcile loop") + err := reconciler.reconcileRemediation(remediationinstance, logger) + Expect(err).To(BeNil()) + By("the remediation should be applied") + foundCM := &corev1.ConfigMap{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: "my-cm", Namespace: "test-ns"}, foundCM) + Expect(err).ToNot(HaveOccurred()) + Expect(foundCM.GetName()).To(Equal("my-cm")) + Expect(foundCM.Data["key"]).To(Equal("val")) + + By("the second remediation should have applied") + rem2 := &compv1alpha1.ComplianceRemediation{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: "testRem-2", Namespace: remediationinstance.Namespace}, rem2) + Expect(err).ToNot(HaveOccurred()) + Expect(rem2.Spec.Apply).To(BeTrue()) + + err = reconciler.reconcileRemediation(rem2, logger) + Expect(err).To(BeNil()) + + foundCM2 := &corev1.ConfigMap{} + err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: "my-cm-2", Namespace: "test-ns"}, foundCM2) + Expect(err).ToNot(HaveOccurred()) + Expect(foundCM2.GetName()).To(Equal("my-cm-2")) + Expect(foundCM2.Data["key"]).To(Equal("val")) + }) + }) + Context("with current MachineConfig remediation object", func() { BeforeEach(func() { mc := &mcfgv1.MachineConfig{