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
pkg/helm/controller/reconcile.go: finalizer fix #3039
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
entries: | ||
- description: > | ||
Helm operator now applies its uninstall finalizer only when | ||
a release is deployed. This fixes a bug that caused the | ||
CR to be unable to be deleted without manually intervening | ||
to delete a prematurely added finalizer. | ||
kind: bugfix |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,9 @@ import ( | |
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apimachinery/pkg/util/wait" | ||
"k8s.io/client-go/tools/record" | ||
"k8s.io/client-go/util/retry" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
||
"github.com/operator-framework/operator-sdk/internal/util/diffutil" | ||
|
@@ -94,38 +96,8 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. | |
status := types.StatusFor(o) | ||
log = log.WithValues("release", manager.ReleaseName()) | ||
|
||
deleted := o.GetDeletionTimestamp() != nil | ||
pendingFinalizers := o.GetFinalizers() | ||
if !deleted && !contains(pendingFinalizers, finalizer) { | ||
log.V(1).Info("Adding finalizer", "finalizer", finalizer) | ||
finalizers := append(pendingFinalizers, finalizer) | ||
o.SetFinalizers(finalizers) | ||
err = r.updateResource(o) | ||
|
||
// Need to requeue because finalizer update does not change metadata.generation | ||
return reconcile.Result{Requeue: true}, err | ||
} | ||
Comment on lines
-97
to
-107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section is moved so that we only add the finalizer after a successful install and then we ensure it is present when reconciling. |
||
|
||
status.SetCondition(types.HelmAppCondition{ | ||
Type: types.ConditionInitialized, | ||
Status: types.StatusTrue, | ||
}) | ||
|
||
if err := manager.Sync(context.TODO()); err != nil { | ||
log.Error(err, "Failed to sync release") | ||
status.SetCondition(types.HelmAppCondition{ | ||
Type: types.ConditionIrreconcilable, | ||
Status: types.StatusTrue, | ||
Reason: types.ReasonReconcileError, | ||
Message: err.Error(), | ||
}) | ||
_ = r.updateResourceStatus(o, status) | ||
return reconcile.Result{}, err | ||
} | ||
status.RemoveCondition(types.ConditionIrreconcilable) | ||
Comment on lines
-109
to
-125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section is moved to after the deletion check and uninstall code. This is necessary to ensure that a CR can always be deleted. There were cases where the CR had a deletion timestamp, but this call to |
||
|
||
if deleted { | ||
if !contains(pendingFinalizers, finalizer) { | ||
if o.GetDeletionTimestamp() != nil { | ||
if !contains(o.GetFinalizers(), finalizer) { | ||
log.Info("Resource is terminated, skipping reconciliation") | ||
return reconcile.Result{}, nil | ||
} | ||
|
@@ -163,13 +135,7 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. | |
return reconcile.Result{}, err | ||
} | ||
|
||
finalizers := []string{} | ||
for _, pendingFinalizer := range pendingFinalizers { | ||
if pendingFinalizer != finalizer { | ||
finalizers = append(finalizers, pendingFinalizer) | ||
} | ||
} | ||
o.SetFinalizers(finalizers) | ||
controllerutil.RemoveFinalizer(o, finalizer) | ||
if err := r.updateResource(o); err != nil { | ||
log.Info("Failed to remove CR uninstall finalizer") | ||
return reconcile.Result{}, err | ||
|
@@ -187,6 +153,24 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. | |
return reconcile.Result{}, nil | ||
} | ||
|
||
status.SetCondition(types.HelmAppCondition{ | ||
Type: types.ConditionInitialized, | ||
Status: types.StatusTrue, | ||
}) | ||
|
||
if err := manager.Sync(context.TODO()); err != nil { | ||
log.Error(err, "Failed to sync release") | ||
status.SetCondition(types.HelmAppCondition{ | ||
Type: types.ConditionIrreconcilable, | ||
Status: types.StatusTrue, | ||
Reason: types.ReasonReconcileError, | ||
Message: err.Error(), | ||
}) | ||
_ = r.updateResourceStatus(o, status) | ||
return reconcile.Result{}, err | ||
} | ||
status.RemoveCondition(types.ConditionIrreconcilable) | ||
|
||
if !manager.IsInstalled() { | ||
for k, v := range r.OverrideValues { | ||
r.EventRecorder.Eventf(o, "Warning", "OverrideValuesInUse", | ||
|
@@ -206,6 +190,13 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. | |
} | ||
status.RemoveCondition(types.ConditionReleaseFailed) | ||
|
||
log.V(1).Info("Adding finalizer", "finalizer", finalizer) | ||
controllerutil.AddFinalizer(o, finalizer) | ||
if err := r.updateResource(o); err != nil { | ||
log.Info("Failed to add CR uninstall finalizer") | ||
return reconcile.Result{}, err | ||
} | ||
|
||
if r.releaseHook != nil { | ||
if err := r.releaseHook(installedRelease); err != nil { | ||
log.Error(err, "Failed to run release hook") | ||
|
@@ -236,6 +227,15 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. | |
return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err | ||
} | ||
|
||
if !contains(o.GetFinalizers(), finalizer) { | ||
log.V(1).Info("Adding finalizer", "finalizer", finalizer) | ||
controllerutil.AddFinalizer(o, finalizer) | ||
if err := r.updateResource(o); err != nil { | ||
log.Info("Failed to add CR uninstall finalizer") | ||
return reconcile.Result{}, err | ||
} | ||
} | ||
|
||
if manager.IsUpdateRequired() { | ||
for k, v := range r.OverrideValues { | ||
r.EventRecorder.Eventf(o, "Warning", "OverrideValuesInUse", | ||
|
@@ -343,12 +343,16 @@ func hasHelmUpgradeForceAnnotation(o *unstructured.Unstructured) bool { | |
} | ||
|
||
func (r HelmOperatorReconciler) updateResource(o runtime.Object) error { | ||
return r.Client.Update(context.TODO(), o) | ||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error { | ||
return r.Client.Update(context.TODO(), o) | ||
}) | ||
Comment on lines
+346
to
+348
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we're no longer using the |
||
} | ||
|
||
func (r HelmOperatorReconciler) updateResourceStatus(o *unstructured.Unstructured, status *types.HelmAppStatus) error { | ||
o.Object["status"] = status | ||
return r.Client.Status().Update(context.TODO(), o) | ||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error { | ||
o.Object["status"] = status | ||
return r.Client.Status().Update(context.TODO(), o) | ||
}) | ||
} | ||
|
||
func (r HelmOperatorReconciler) waitForDeletion(o runtime.Object) error { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
GenerationChangedPredicate
needed to be removed. Without it, the helm operator can now detect that the finalizer has been changed/deleted and reconcile so that it is re-added.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this predicate preventing detection of changes to the finalizer? Wouldn't a finalizer update change a CR's generation and pass through the predicate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty sure generation is only changed for spec updates (and maybe status updates). It is not changed when metadata changes. I'm like 95% certain on this, so I'll double check to be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep just confirmed that on
master
I can manually remove the finalizer and it doesn't show back up until I make another edit that changes the spec.