Skip to content

Commit

Permalink
delete required related objects when necessary
Browse files Browse the repository at this point in the history
Signed-off-by: Will Kutler <wkutler@redhat.com>
  • Loading branch information
willkutler authored and openshift-merge-robot committed Jul 21, 2022
1 parent 263f9b9 commit 531029a
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 12 deletions.
156 changes: 155 additions & 1 deletion controllers/configurationpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,88 @@ func (r *ConfigurationPolicyReconciler) getObjectTemplateDetails(
return templateObjs, selectedNamespaces, false, nil
}

func (r *ConfigurationPolicyReconciler) cleanUpChildObjects(plc policyv1.ConfigurationPolicy) []string {
deletionFailures := []string{}

for _, object := range plc.Status.RelatedObjects {
// set up client for object deletion
gvk := schema.FromAPIVersionAndKind(object.Object.APIVersion, object.Object.Kind)

log := log.WithValues("policy", plc.GetName(), "groupVersionKind", gvk.String())

mapper := restmapper.NewDiscoveryRESTMapper(r.apiGroups)

mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
log.Error(err, "Could not get resource mapping for child object")

deletionFailures = append(deletionFailures, gvk.String()+fmt.Sprintf(` "%s" in namespace %s`,
object.Object.Metadata.Name, object.Object.Metadata.Namespace))

continue
}

dclient, gvr := r.getResourceAndDynamicClient(mapping)

namespaced := object.Object.Metadata.Namespace != ""

// determine whether object should be deleted
needsDelete := false

if strings.EqualFold(string(plc.Spec.RemediationAction), "enforce") {
if string(plc.Spec.PruneObjectBehavior) == "DeleteAll" {
needsDelete = true
} else {
// if prune behavior is DeleteIfCreated, we need to check whether createdByPolicy
// is true and the UID is not stale
existing, _ := getObject(
namespaced,
object.Object.Metadata.Namespace,
object.Object.Metadata.Name,
gvr,
dclient)

if existing == nil {
continue
}

uid, uidIsString, err := unstructured.NestedString(existing.Object, "metadata", "uid")

if !uidIsString || err != nil {
log.Error(err, "Tried to pull UID from existing obj but the field is not a string")
} else if object.Properties != nil &&
object.Properties.CreatedByPolicy != nil &&
*object.Properties.CreatedByPolicy &&
object.Properties.UID == uid {
needsDelete = true
}
}
}

// delete object if needed
if needsDelete {
var res dynamic.ResourceInterface
if namespaced {
res = dclient.Resource(gvr).Namespace(object.Object.Metadata.Namespace)
} else {
res = dclient.Resource(gvr)
}

if completed, err := deleteObject(res, object.Object.Metadata.Name,
object.Object.Metadata.Namespace); !completed {
deletionFailures = append(deletionFailures, gvk.String()+fmt.Sprintf(` "%s" in namespace %s`,
object.Object.Metadata.Name, object.Object.Metadata.Namespace))

log.Error(err, "Error: Failed to delete object during child object pruning")
} else {
log.Info("Object successfully deleted as part of child object pruning")
}
}
}

return deletionFailures
}

// handleObjectTemplates iterates through all policy templates in a given policy and processes them
func (r *ConfigurationPolicyReconciler) handleObjectTemplates(plc policyv1.ConfigurationPolicy) {
log := log.WithValues("policy", plc.GetName())
Expand All @@ -429,6 +511,68 @@ func (r *ConfigurationPolicyReconciler) handleObjectTemplates(plc policyv1.Confi
return
}

pruneObjectFinalizer := "policy.open-cluster-management.io/delete-related-objects"

// object handling for when configurationPolicy is deleted
if plc.Spec.PruneObjectBehavior == "DeleteIfCreated" || plc.Spec.PruneObjectBehavior == "DeleteAll" {
// set finalizer if it hasn't been set
if !configPlcHasFinalizer(plc, pruneObjectFinalizer) {
plc.SetFinalizers(addConfigPlcFinalizer(plc, pruneObjectFinalizer))

err := r.Update(context.TODO(), &plc)
if err != nil {
log.V(1).Error(err, "Error setting finalizer for configuration policy", plc)
}
}

// kick off object deletion if configurationPolicy has been deleted
if plc.ObjectMeta.DeletionTimestamp != nil {
log.V(1).Info("Config policy has been deleted, handling child objects")

failures := r.cleanUpChildObjects(plc)

if len(failures) == 0 {
log.V(1).Info("Objects have been successfully cleaned up, removing finalizer")
plc.SetFinalizers(removeConfigPlcFinalizer(plc, pruneObjectFinalizer))

err := r.Update(context.TODO(), &plc)
if err != nil {
log.V(1).Error(err, "Error unsetting finalizer for configuration policy", plc)
}
} else {
log.V(1).Info("Object cleanup failed, some objects have not been deleted from the cluster")

statusChanged := addConditionToStatus(
&plc,
0,
false,
"Error cleaning up child objects",
"Failed to delete objects: "+strings.Join(failures, ", "))
if statusChanged {
parentStatusUpdateNeeded = true

r.Recorder.Event(
&plc,
eventWarning,
fmt.Sprintf(plcFmtStr, plc.GetName()),
convertPolicyStatusToString(&plc),
)
}

r.checkRelatedAndUpdate(plc, relatedObjects, oldRelated, parentStatusUpdateNeeded)
}

return
}
} else if configPlcHasFinalizer(plc, pruneObjectFinalizer) {
// if pruneObjectBehavior is none, no finalizer is needed
plc.SetFinalizers(removeConfigPlcFinalizer(plc, pruneObjectFinalizer))
err := r.Update(context.TODO(), &plc)
if err != nil {
log.V(1).Error(err, "Error unsetting finalizer for configuration policy", plc)
}
}

addTemplateErrorViolation := func(reason, msg string) {
log.Info("Setting the policy to noncompliant due to a templating error", "error", msg)

Expand Down Expand Up @@ -996,6 +1140,7 @@ func (r *ConfigurationPolicyReconciler) handleObjects(
relatedObjects = addRelatedObjects(
compliant,
rsrc,
objDetails.kind,
namespace,
objDetails.isNamespaced,
[]string{name},
Expand All @@ -1021,7 +1166,16 @@ func (r *ConfigurationPolicyReconciler) handleObjects(
}
}

relatedObjects = addRelatedObjects(compliant, rsrc, namespace, objDetails.isNamespaced, objNames, reason, nil)
relatedObjects = addRelatedObjects(
compliant,
rsrc,
objDetails.kind,
namespace,
objDetails.isNamespaced,
objNames,
reason,
nil,
)

if !statusUpdateNeeded {
log.V(2).Info("The status did not change for this object template")
Expand Down
25 changes: 16 additions & 9 deletions controllers/configurationpolicy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,21 +319,23 @@ func TestAddRelatedObject(t *testing.T) {
namespaced := true
name := "foo"
reason := "reason"
relatedList := addRelatedObjects(compliant, rsrc, namespace, namespaced, []string{name}, reason, nil)
relatedList := addRelatedObjects(compliant, rsrc, "ConfigurationPolicy",
namespace, namespaced, []string{name}, reason, nil)
related := relatedList[0]

// get the related object and validate what we added is in the status
assert.True(t, related.Compliant == string(policyv1.Compliant))
assert.True(t, related.Reason == "reason")
assert.True(t, related.Object.APIVersion == rsrc.GroupVersion().String())
assert.True(t, related.Object.Kind == rsrc.Resource)
assert.True(t, related.Object.Kind == "ConfigurationPolicy")
assert.True(t, related.Object.Metadata.Name == name)
assert.True(t, related.Object.Metadata.Namespace == namespace)

// add the same object and make sure the existing one is overwritten
reason = "new"
compliant = false
relatedList = addRelatedObjects(compliant, rsrc, namespace, namespaced, []string{name}, reason, nil)
relatedList = addRelatedObjects(compliant, rsrc, "ConfigurationPolicy",
namespace, namespaced, []string{name}, reason, nil)
related = relatedList[0]

assert.True(t, len(relatedList) == 1)
Expand All @@ -343,7 +345,8 @@ func TestAddRelatedObject(t *testing.T) {
// add a new related object and make sure the entry is appended
name = "bar"
relatedList = append(relatedList,
addRelatedObjects(compliant, rsrc, namespace, namespaced, []string{name}, reason, nil)...)
addRelatedObjects(compliant, rsrc, "ConfigurationPolicy",
namespace, namespaced, []string{name}, reason, nil)...)

assert.True(t, len(relatedList) == 2)

Expand Down Expand Up @@ -375,28 +378,32 @@ func TestSortRelatedObjectsAndUpdate(t *testing.T) {
}
rsrc := policyv1.SchemeBuilder.GroupVersion.WithResource("ConfigurationPolicy")
name := "foo"
relatedList := addRelatedObjects(true, rsrc, "default", true, []string{name}, "reason", nil)
relatedList := addRelatedObjects(true, rsrc, "ConfigurationPolicy", "default", true, []string{name}, "reason", nil)

// add the same object but after sorting it should be first
name = "bar"
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "default", true, []string{name}, "reason", nil)...)
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "ConfigurationPolicy", "default",
true, []string{name}, "reason", nil)...)

empty := []policyv1.RelatedObject{}

sortRelatedObjectsAndUpdate(policy, relatedList, empty)
assert.True(t, relatedList[0].Object.Metadata.Name == "bar")

// append another object named bar but also with namespace bar
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "bar", true, []string{name}, "reason", nil)...)
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "ConfigurationPolicy", "bar",
true, []string{name}, "reason", nil)...)

sortRelatedObjectsAndUpdate(policy, relatedList, empty)
assert.True(t, relatedList[0].Object.Metadata.Namespace == "bar")

// clear related objects and test sorting with no namespace
name = "foo"
relatedList = addRelatedObjects(true, rsrc, "", false, []string{name}, "reason", nil)
relatedList = addRelatedObjects(true, rsrc, "ConfigurationPolicy", "",
false, []string{name}, "reason", nil)
name = "bar"
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "", false, []string{name}, "reason", nil)...)
relatedList = append(relatedList, addRelatedObjects(true, rsrc, "ConfigurationPolicy", "",
false, []string{name}, "reason", nil)...)

sortRelatedObjectsAndUpdate(policy, relatedList, empty)
assert.True(t, relatedList[0].Object.Metadata.Name == "bar")
Expand Down
33 changes: 32 additions & 1 deletion controllers/configurationpolicy_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
func addRelatedObjects(
compliant bool,
rsrc schema.GroupVersionResource,
kind string,
namespace string,
namespaced bool,
objNames []string,
Expand Down Expand Up @@ -53,7 +54,7 @@ func addRelatedObjects(
}

relatedObject.Object.APIVersion = rsrc.GroupVersion().String()
relatedObject.Object.Kind = rsrc.Resource
relatedObject.Object.Kind = kind
relatedObject.Object.Metadata = metadata
relatedObjects = updateRelatedObjectsStatus(relatedObjects, relatedObject)
}
Expand Down Expand Up @@ -528,3 +529,33 @@ func sortAndJoinKeys(m map[string]bool, sep string) string {

return strings.Join(keys, sep)
}

func configPlcHasFinalizer(plc policyv1.ConfigurationPolicy, finalizer string) bool {
for _, existingFinalizer := range plc.GetFinalizers() {
if existingFinalizer == finalizer {
return true
}
}

return false
}

func addConfigPlcFinalizer(plc policyv1.ConfigurationPolicy, finalizer string) []string {
if configPlcHasFinalizer(plc, finalizer) {
return plc.GetFinalizers()
}

return append(plc.GetFinalizers(), finalizer)
}

func removeConfigPlcFinalizer(plc policyv1.ConfigurationPolicy, finalizer string) []string {
result := []string{}

for _, existingFinalizer := range plc.GetFinalizers() {
if existingFinalizer != finalizer {
result = append(result, existingFinalizer)
}
}

return result
}
Loading

0 comments on commit 531029a

Please sign in to comment.