diff --git a/controllers/installation_controller.go b/controllers/installation_controller.go index db5f9700..5ae2a587 100644 --- a/controllers/installation_controller.go +++ b/controllers/installation_controller.go @@ -133,6 +133,10 @@ func (r *InstallationReconciler) Reconcile(ctx context.Context, req ctrl.Request err = r.uninstallInstallation(ctx, log, inst) log.V(Log4Debug).Info("Reconciliation complete: A porter agent has been dispatched to uninstall the installation.") return ctrl.Result{}, err + } else if r.shouldOrphan(inst) { + log.V(Log4Debug).Info("Reconciliation complete: Your installation is being deleted. Please clean up installation resources") + err = removeFinalizer(ctx, log, r.Client, inst) + return ctrl.Result{}, err } else if isDeleted(inst) { // This is installation without a finalizer that was deleted We remove the // finalizer after we successfully uninstall (or someone is manually cleaning @@ -401,7 +405,11 @@ func (r *InstallationReconciler) saveStatus(ctx context.Context, log logr.Logger func (r *InstallationReconciler) shouldUninstall(inst *v1.Installation) bool { // ignore a deleted CRD with no finalizers - return isDeleted(inst) && isFinalizerSet(inst) + return isDeleted(inst) && isFinalizerSet(inst) && inst.GetAnnotations()[v1.PorterDeletePolicyAnnotation] == v1.PorterDeletePolicyDelete +} + +func (r *InstallationReconciler) shouldOrphan(inst *v1.Installation) bool { + return isDeleted(inst) && isFinalizerSet(inst) && inst.GetAnnotations()[v1.PorterDeletePolicyAnnotation] == v1.PorterDeletePolicyOrphan } // Sync the retry annotation from the installation to the agent action to trigger another run. diff --git a/controllers/installation_controller_test.go b/controllers/installation_controller_test.go index 57442064..8ad20c3f 100644 --- a/controllers/installation_controller_test.go +++ b/controllers/installation_controller_test.go @@ -49,6 +49,7 @@ func TestShouldInstall(t *testing.T) { Namespace: "fake-ns", Finalizers: []string{v1.FinalizerName}, DeletionTimestamp: test.delTimeStamp, + Annotations: map[string]string{v1.PorterDeletePolicyAnnotation: v1.PorterDeletePolicyDelete}, }, } rec := setupInstallationController(inst) @@ -63,6 +64,38 @@ func TestShouldInstall(t *testing.T) { } } +func TestShouldOrphan(t *testing.T) { + now := metav1.Now() + tests := map[string]struct { + wantTrue bool + delTimeStamp *metav1.Time + }{ + "true": {wantTrue: true, delTimeStamp: &now}, + "false": {wantTrue: false, delTimeStamp: nil}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + inst := &v1.Installation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-name", + Namespace: "fake-ns", + Finalizers: []string{v1.FinalizerName}, + DeletionTimestamp: test.delTimeStamp, + Annotations: map[string]string{v1.PorterDeletePolicyAnnotation: v1.PorterDeletePolicyOrphan}, + }, + } + rec := setupInstallationController(inst) + isTrue := rec.shouldOrphan(inst) + if test.wantTrue { + assert.True(t, isTrue) + } + if !test.wantTrue { + assert.False(t, isTrue) + } + }) + } +} + func TestUninstallInstallation(t *testing.T) { ctx := context.Background() inst := &v1.Installation{