Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Critical Bug]unexpectedly deleted when pruneObjectBehavior is None #142

Conversation

yiraeChristineKim
Copy link
Contributor

Description of problem:

When a ConfigurationPolicy stops managing an object, it is always deleted regardless of pruneObjectBehavior being set to None.

Below are a few examples of when a ConfigurationPolicy would stop managing an object:

The ConfigurationPolicy is updated to manage a different object.
The ConfigurationPolicy's namespaceSelector is changed.
The ConfigurationPolicy's templating resolves differently based on changing conditions to no longer manage an object.
Version-Release number of selected component (if applicable):
How reproducible:

Very reproducable

Steps to Reproduce:
Create a ConfigurationPolicy with pruneObjectBehavior set to "None" (default) that creates/updates a ConfigMap called "test"
Update the ConfigurationPolicy to create/update a ConfigMap called "not test"
Actual results:

After following the reproducing steps, the ConfigMap called "test" is deleted.

Expected results:

After following the reproducing steps, the ConfigMap called "test" should not be deleted.

Additional info:

This will cause unexpected data loss to customers.

Ref: https://issues.redhat.com/browse/ACM-5939

Copy link
Member

@JustinKuli JustinKuli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the extra file, I think this looks right! Feel free to ignore my suggestion, I might have gotten a bit carried away...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't want this

// should compare only object
for _, r := range arr {
func containRelated(related []policyv1.RelatedObject, input policyv1.RelatedObject) bool {
// should compare name, APIVersion, Kind and namepcace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// should compare name, APIVersion, Kind and namepcace
// should compare name, APIVersion, Kind and namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good

Comment on lines 509 to 540
func (r *ConfigurationPolicyReconciler) cleanUpChildObjects(plc policyv1.ConfigurationPolicy,
related []policyv1.RelatedObject,
) []string {
deletionFailures := []string{}

// When spec is updated and new related objects are created
if len(related) != 0 {
log.Info("preparing to delete detached objects...")

oldRelated := plc.Status.RelatedObjects
var objShouldRemoved []policyv1.RelatedObject

for _, oldR := range oldRelated {
exist := containRelated(related, oldR)

if !exist {
objShouldRemoved = append(objShouldRemoved, oldR)
}
}

plc.Status.RelatedObjects = objShouldRemoved
}

for _, object := range plc.Status.RelatedObjects {
// set up client for object deletion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking out loud here in case this is cleaner (it is to me at least)... what do you think of this?

func (r *ConfigurationPolicyReconciler) cleanUpChildObjects(plc policyv1.ConfigurationPolicy,
	newRelated []policyv1.RelatedObject,
) []string {
	deletionFailures := []string{}
	objsToDelete := []policyv1.RelatedObject{}
	
    log.Info("preparing to delete detached objects...")

	if len(newRelated) == 0 { // delete all
	    objsToDelete = plc.Status.RelatedObjects
	} else { // When spec is updated and new related objects are created
		oldRelated := plc.Status.RelatedObjects

		for _, oldR := range oldRelated {
			if !containRelated(newRelated, oldR) {
				objsToDelete = append(objsToDelete, oldR)
			}
		}
	}

	for _, object := range objsToDelete {
		// set up client for object deletion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the two cases can just work the same, because if the newRelated is empty, then containRelated will just always be false.

func (r *ConfigurationPolicyReconciler) cleanUpChildObjects(plc policyv1.ConfigurationPolicy,
	newRelated []policyv1.RelatedObject,
) []string {
	deletionFailures := []string{}
	objsToDelete := []policyv1.RelatedObject{}
	
    log.Info("preparing to delete detached objects...")
    
	oldRelated := plc.Status.RelatedObjects
	for _, oldR := range oldRelated {
		if !containRelated(newRelated, oldR) {
			objsToDelete = append(objsToDelete, oldR)
		}
	}
		
	for _, object := range objsToDelete {
		// set up client for object deletion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also logic is same but change is variable name right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when newRelated = nil we have to fall in loop, unnecessary isn't it?

Comment on lines 519 to 536
var objShouldRemoved []policyv1.RelatedObject

for _, oldR := range objsToDelete {
exist := containRelated(newRelated, oldR)

if !exist {
objShouldRemoved = append(objShouldRemoved, oldR)
}
}

objsToDelete = objShouldRemoved
Copy link
Member

@mprahl mprahl Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional simplification

Suggested change
var objShouldRemoved []policyv1.RelatedObject
for _, oldR := range objsToDelete {
exist := containRelated(newRelated, oldR)
if !exist {
objShouldRemoved = append(objShouldRemoved, oldR)
}
}
objsToDelete = objShouldRemoved
objsToDelete = []policyv1.RelatedObject{}
for _, oldR := range plc.Status.RelatedObjects {
if !containRelated(newRelated, oldR) {
objsToDelete = append(objsToDelete, oldR)
}
}

Comment on lines 792 to 793
// At here, relatedObjects is always []
failures := r.cleanUpChildObjects(plc, relatedObjects)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is clearer?

Suggested change
// At here, relatedObjects is always []
failures := r.cleanUpChildObjects(plc, relatedObjects)
failures := r.cleanUpChildObjects(plc, nil)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good

@@ -3,6 +3,7 @@ kind: ConfigurationPolicy
metadata:
name: tmplt-policy-secret-duplicate
spec:
pruneObjectBehavior: DeleteAll
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pruneObjectBehavior : none template error always fall to needsDelete = false

Copy link
Contributor Author

@yiraeChristineKim yiraeChristineKim Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more targeted test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had requested this :)

If this test doesn't have pruneObjectBehavior set, then the template error definitely shouldn't cause anything to be removed (because now, things are only removed when that is set). The test is meant to answer the question "Will objects be removed when there is a templating error, and I generally want objects to be pruned?" with "No, objects will not be removed".

@@ -571,6 +591,8 @@ func (r *ConfigurationPolicyReconciler) cleanUpChildObjects(plc policyv1.Configu
object.Properties.UID == uid {
needsDelete = true
}
} else { // None or not
needsDelete = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is happening too late in the method since the code above is performing API queries only for the return values to never get used.

My suggestion is to add something to this effect in the start of the method:

if !strings.EqualFold(string(plc.Spec.RemediationAction), "enforce") {
	return deletionFailures
}

if !(string(plc.Spec.PruneObjectBehavior) == "DeleteAll" || string(plc.Spec.PruneObjectBehavior) == "DeleteIfCreated") {
	return deletionFailures
}

Afterwards, you can also remove the if statement of if strings.EqualFold(string(plc.Spec.RemediationAction), "enforce") { on line 581 of the diff.

Copy link
Member

@mprahl mprahl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good but just a performance concern and a few optional improvements.


// When spec is updated and new related objects are created
if len(newRelated) != 0 {
log.Info("preparing to delete detached objects...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional:

Suggested change
log.Info("preparing to delete detached objects...")
log.Info("Preparing to delete detached objects...")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this log message only be shown if objsToDelete has entries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

um.... you right. let me delete it

@@ -767,7 +794,8 @@ func (r *ConfigurationPolicyReconciler) handleObjectTemplates(plc policyv1.Confi
if plc.ObjectMeta.DeletionTimestamp != nil {
log.V(1).Info("Config policy has been deleted, handling child objects")

failures := r.cleanUpChildObjects(plc)
// At here, relatedObjects is always []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// At here, relatedObjects is always []

Copy link
Member

@mprahl mprahl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments

Signed-off-by: Yi Rae Kim <yikim@redhat.com>
Copy link
Member

@mprahl mprahl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/hold for review from Justin

@JustinKuli
Copy link
Member

/unohold

@openshift-ci
Copy link

openshift-ci bot commented Jun 15, 2023

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: JustinKuli, mprahl, yiraeChristineKim

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [JustinKuli,mprahl,yiraeChristineKim]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@mprahl
Copy link
Member

mprahl commented Jun 15, 2023

/unhold

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants