Skip to content

Commit

Permalink
Fire event on object update
Browse files Browse the repository at this point in the history
Signed-off-by: Will Kutler <wkutler@redhat.com>
  • Loading branch information
willkutler committed Jan 10, 2023
1 parent 1f2a2e8 commit 9f22a7c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 14 deletions.
75 changes: 61 additions & 14 deletions controllers/configurationpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,17 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
if !exists && obj.shouldExist {
// it is a musthave and it does not exist, so it must be created
if strings.EqualFold(string(remediation), string(policyv1.Enforce)) {
// object is missing, so send noncompliant event
_ = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, false, true)
obj.policy.Status.ComplianceState = policyv1.NonCompliant
statusStr := convertPolicyStatusToString(obj.policy)
objLog.Info("Sending an update policy status event", "policy", obj.policy.Name, "status", statusStr)
r.Recorder.Event(obj.policy, eventWarning, fmt.Sprintf(eventFmtStr, obj.policy.GetName(), obj.name),
statusStr)
// update parent policy status
r.addForUpdate(obj.policy, true)

var uid string
statusUpdateNeeded, uid, err = r.enforceByCreatingOrDeleting(obj)

Expand All @@ -1471,6 +1482,7 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
CreatedByPolicy: &created,
UID: uid,
}
compliant = true
}
} else { // inform
compliant = false
Expand Down Expand Up @@ -1514,7 +1526,21 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(

before := time.Now().UTC()

throwSpecViolation, msg, pErr := r.checkAndUpdateResource(obj, compType, mdCompType, remediation)
throwSpecViolation, msg, pErr, triedUpdate, updatedObj := r.checkAndUpdateResource(obj, compType, mdCompType,
remediation)

if triedUpdate {
// object has a mismatch and needs an update to be enforced, throw violation for mismatch
_ = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy, obj.index,
false, true)
obj.policy.Status.ComplianceState = policyv1.NonCompliant
statusStr := convertPolicyStatusToString(obj.policy)
objLog.Info("Sending an update policy status event", "policy", obj.policy.Name, "status", statusStr)
r.Recorder.Event(obj.policy, eventWarning, fmt.Sprintf(eventFmtStr, obj.policy.GetName(), obj.name),
statusStr)
// update parent policy status
r.addForUpdate(obj.policy, true)
}

duration := time.Now().UTC().Sub(before)
seconds := float64(duration) / float64(time.Second)
Expand All @@ -1538,8 +1564,17 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
// it is a must have and it does exist, so it is compliant
compliant = true
if strings.EqualFold(string(remediation), string(policyv1.Enforce)) {
statusUpdateNeeded = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, compliant, true)
if updatedObj {
// object updated in checkAndUpdateResource, send event
reason := "K8s update success"
idStr := identifierStr([]string{obj.name}, obj.namespace, obj.namespaced)
msg := fmt.Sprintf("%v %v was updated successfully", obj.gvr.Resource, idStr)

statusUpdateNeeded = addConditionToStatus(obj.policy, obj.index, true, reason, msg)
} else {
statusUpdateNeeded = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, compliant, true)
}
created := false
creationInfo = &policyv1.ObjectProperties{
CreatedByPolicy: &created,
Expand All @@ -1559,7 +1594,14 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
compliant = false
}

if compliant {
obj.policy.Status.ComplianceState = policyv1.Compliant
} else {
obj.policy.Status.ComplianceState = policyv1.NonCompliant
}

statusStr := convertPolicyStatusToString(obj.policy)

log.V(1).Info("Sending an update policy status event", "policy", obj.policy.Name, "status", statusStr)
r.Recorder.Event(obj.policy, eventType, fmt.Sprintf(eventFmtStr, obj.policy.GetName(), obj.name), statusStr)

Expand Down Expand Up @@ -1845,6 +1887,8 @@ func (r *ConfigurationPolicyReconciler) enforceByCreatingOrDeleting(obj singleOb
if !uidIsString || err != nil {
log.Error(err, "Tried to set UID in status but the field is not a string")
}

completed = true
}
} else {
log.Info("Enforcing the policy by deleting the object")
Expand Down Expand Up @@ -2319,21 +2363,23 @@ func (r *ConfigurationPolicyReconciler) validateObject(object *unstructured.Unst
}

// checkAndUpdateResource checks each individual key of a resource and passes it to handleKeys to see if it
// matches the template and update it if the remediationAction is enforce
// matches the template and update it if the remediationAction is enforce. UpdateNeeded indicates whether the
// function tried to update the child object and updateSucceeded indicates whether the update was applied
// successfully.
func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(
obj singleObject,
complianceType string,
mdComplianceType string,
remediation policyv1.RemediationAction,
) (throwSpecViolation bool, message string, processingErr bool) {
) (throwSpecViolation bool, message string, processingErr bool, updateNeeded bool, updateSucceeded bool) {
log := log.WithValues(
"policy", obj.policy.Name, "name", obj.name, "namespace", obj.namespace, "resource", obj.gvr.Resource,
)

if obj.object == nil {
log.Info("Skipping update: Previous object retrieval from the API server failed")

return false, "", false
return false, "", false, false, false
}

var res dynamic.ResourceInterface
Expand All @@ -2344,9 +2390,10 @@ func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(
}

var err error
var updateNeeded bool
var statusUpdated bool

updateSucceeded = false

for key := range obj.unstruct.Object {
isStatus := key == "status"

Expand All @@ -2363,7 +2410,7 @@ func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(
if errorMsg != "" {
log.Info(errorMsg)

return false, errorMsg, true
return false, errorMsg, true, false, false
}

if mergedObj == nil && skipped {
Expand All @@ -2382,7 +2429,7 @@ func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(

if keyUpdateNeeded {
if strings.EqualFold(string(remediation), string(policyv1.Inform)) {
return true, "", false
return true, "", false, false, false
} else if isStatus {
statusUpdated = true
log.Info("Ignoring an update to the object status", "key", key)
Expand All @@ -2399,26 +2446,26 @@ func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(
if err := r.validateObject(obj.object); err != nil {
message := fmt.Sprintf("Error validating the object %s, the error is `%v`", obj.name, err)

return false, message, true
return false, message, true, updateNeeded, false
}

_, err = res.Update(context.TODO(), obj.object, metav1.UpdateOptions{})
if k8serrors.IsNotFound(err) {
message := fmt.Sprintf("`%v` is not present and must be created", obj.object.GetKind())

return false, message, true
return false, message, true, updateNeeded, false
}

if err != nil {
message := fmt.Sprintf("Error updating the object `%v`, the error is `%v`", obj.name, err)

return false, message, true
return false, message, true, updateNeeded, false
}

log.Info("Updated the object based on the template definition")
updateSucceeded = true
}

return statusUpdated, "", false
return statusUpdated, "", false, updateNeeded, updateSucceeded
}

// AppendCondition check and appends conditions to the policy status
Expand Down
51 changes: 51 additions & 0 deletions test/e2e/case27_showupdateinstatus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2020 Red Hat, Inc.
// Copyright Contributors to the Open Cluster Management project

package e2e

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"open-cluster-management.io/config-policy-controller/test/utils"
)

const (
case27ConfigPolicyName string = "policy-cfgmap-create"
case27CreateYaml string = "../resources/case27_showupdateinstatus/case27-create-cfgmap-policy.yaml"
case27UpdateYaml string = "../resources/case27_showupdateinstatus/case27-update-cfgmap-policy.yaml"
)

var _ = Describe("Verify status update after updating object", Ordered, func() {
It("configmap should be created properly on the managed cluster", func() {
By("Creating " + case27ConfigPolicyName + " on managed")
utils.Kubectl("apply", "-f", case27CreateYaml, "-n", testNamespace)
plc := utils.GetWithTimeout(clientManagedDynamic, gvrConfigPolicy,
case27ConfigPolicyName, testNamespace, true, defaultTimeoutSeconds)
Expect(plc).NotTo(BeNil())
Eventually(func() interface{} {
managedPlc := utils.GetWithTimeout(clientManagedDynamic, gvrConfigPolicy,
case27ConfigPolicyName, testNamespace, true, defaultTimeoutSeconds)

return utils.GetStatusMessage(managedPlc)
}, 120, 1).Should(Equal(
"configmaps [case27-map] in namespace default found as specified, " +
"therefore this Object template is compliant"))
})
It("configmap and status should be updated properly on the managed cluster", func() {
By("Updating " + case27ConfigPolicyName + " on managed")
utils.Kubectl("apply", "-f", case27UpdateYaml, "-n", testNamespace)
Eventually(func() interface{} {
managedPlc := utils.GetWithTimeout(clientManagedDynamic, gvrConfigPolicy,
case27ConfigPolicyName, testNamespace, true, defaultTimeoutSeconds)

return utils.GetStatusMessage(managedPlc)
}, 30, 0.5).Should(Equal(
"configmaps [case27-map] in namespace default was updated successfully"))
})

AfterAll(func() {
deleteConfigPolicies([]string{case27ConfigPolicyName})
utils.Kubectl("delete", "configmap", "case27-map")
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-cfgmap-create
spec:
remediationAction: enforce
namespaceSelector:
exclude: ["kube-*"]
include: ["default"]
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
kind: ConfigMap
metadata:
name: case27-map
data:
fieldToUpdate: "1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-cfgmap-create
spec:
remediationAction: enforce
namespaceSelector:
exclude: ["kube-*"]
include: ["default"]
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
kind: ConfigMap
metadata:
name: case27-map
data:
fieldToUpdate: "2"

0 comments on commit 9f22a7c

Please sign in to comment.