diff --git a/api/v1/installation_types.go b/api/v1/installation_types.go index f5ae789d..7a66154f 100644 --- a/api/v1/installation_types.go +++ b/api/v1/installation_types.go @@ -11,8 +11,11 @@ import ( ) const ( - Prefix = "getporter.org/" - AnnotationRetry = Prefix + "retry" + Prefix = "getporter.org/" + AnnotationRetry = Prefix + "retry" + PorterDeletePolicyAnnotation = "getporter.org/deletion-policy" + PorterDeletePolicyDelete = "Delete" + PorterDeletePolicyOrphan = "Orphan" ) // We marshal installation spec to yaml when converting to a porter object diff --git a/controllers/installation_controller.go b/controllers/installation_controller.go index c1ed5025..98b8f768 100644 --- a/controllers/installation_controller.go +++ b/controllers/installation_controller.go @@ -157,6 +157,13 @@ func (r *InstallationReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } + log.V(Log4Debug).Info("Reconciliation complete: A porter agent has been dispatched to apply changes to the installation.") + // NOTE: If this is nil, it will use the default policy of Delete + err = r.applyDeletionPolicy(ctx, log, inst, inst.GetAnnotations()[v1.PorterDeletePolicyAnnotation]) + if err != nil { + return ctrl.Result{}, err + } + log.V(Log4Debug).Info("Reconciliation complete: A porter agent has been dispatched to apply changes to the installation.") if r.PorterGRPCClient != nil { return r.CheckOrCreateInstallationOutputsCR(ctx, log, inst) @@ -404,3 +411,27 @@ func (r *InstallationReconciler) retry(ctx context.Context, log logr.Logger, ins log.V(Log4Debug).Info("Retried associated porter agent action", "name", "retry", action.Name, retry) return nil } + +func (r *InstallationReconciler) applyDeletionPolicy(ctx context.Context, log logr.Logger, inst *v1.Installation, policy string) error { + log.V(Log5Trace).Info("updating deletion policy") + annotations := inst.GetAnnotations() + if len(annotations) < 1 { + annotations = map[string]string{} + } + + if _, ok := annotations[v1.PorterDeletePolicyAnnotation]; !ok { + annotations[v1.PorterDeletePolicyAnnotation] = v1.PorterDeletePolicyDelete + inst.SetAnnotations(annotations) + return r.Update(ctx, inst) + } + + if policy != v1.PorterDeletePolicyOrphan && policy != v1.PorterDeletePolicyDelete { + annotations[v1.PorterDeletePolicyAnnotation] = v1.PorterDeletePolicyDelete + inst.SetAnnotations(annotations) + return r.Update(ctx, inst) + } + + annotations[v1.PorterDeletePolicyAnnotation] = policy + inst.SetAnnotations(annotations) + return r.Update(ctx, inst) +} diff --git a/controllers/installation_controller_test.go b/controllers/installation_controller_test.go index 691f27b2..c443a566 100644 --- a/controllers/installation_controller_test.go +++ b/controllers/installation_controller_test.go @@ -523,3 +523,67 @@ func TestIsHandled(t *testing.T) { _, _, err := r.isHandled(ctx, logr.Discard(), inst) assert.Error(t, err) } + +func TestApplyDeletionPolicyWithNoAnnotation(t *testing.T) { + tests := map[string]struct { + wantPolicy string + policy string + }{ + "empty-string": {policy: "", wantPolicy: v1.PorterDeletePolicyDelete}, + "policy-orphan": {policy: v1.PorterDeletePolicyOrphan, wantPolicy: v1.PorterDeletePolicyOrphan}, + "policy-delete": {policy: v1.PorterDeletePolicyDelete, wantPolicy: v1.PorterDeletePolicyDelete}, + } + ctx := context.Background() + inst := &v1.Installation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-install", + Namespace: "fake-ns", + }, + Spec: v1.InstallationSpec{ + Name: "fake-install", + Namespace: "fake-ns", + }, + } + + scheme := runtime.NewScheme() + utilruntime.Must(v1.AddToScheme(scheme)) + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(inst).WithStatusSubresource(inst).Build() + rc := &InstallationReconciler{ + Client: fakeClient, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := rc.applyDeletionPolicy(ctx, logr.Discard(), inst, test.policy) + assert.NoError(t, err) + assert.Equal(t, inst.GetAnnotations()[v1.PorterDeletePolicyAnnotation], test.wantPolicy) + }) + } +} + +func TestApplyDeletionPolicyWithAnnotation(t *testing.T) { + ctx := context.Background() + inst := &v1.Installation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-install", + Namespace: "fake-ns", + Annotations: map[string]string{ + v1.PorterDeletePolicyAnnotation: v1.PorterDeletePolicyDelete, + }, + }, + Spec: v1.InstallationSpec{ + Name: "fake-install", + Namespace: "fake-ns", + }, + } + + scheme := runtime.NewScheme() + utilruntime.Must(v1.AddToScheme(scheme)) + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(inst).WithStatusSubresource(inst).Build() + rc := &InstallationReconciler{ + Client: fakeClient, + } + + err := rc.applyDeletionPolicy(ctx, logr.Discard(), inst, "") + assert.NoError(t, err) + assert.Equal(t, inst.GetAnnotations()[v1.PorterDeletePolicyAnnotation], v1.PorterDeletePolicyDelete) +}