From 233b48b7153527d80d4634003f8d9d30f3a4564f Mon Sep 17 00:00:00 2001 From: michaeljguarino Date: Mon, 8 Jul 2024 09:40:30 -0400 Subject: [PATCH] Delete project via operator (#1151) --- controller/go.mod | 2 +- controller/go.sum | 2 + controller/internal/client/console.go | 1 + controller/internal/client/project.go | 5 ++ .../namespace_credentials_controller.go | 2 +- .../internal/controller/project_controller.go | 26 +++++++--- .../controller/service_account_controller.go | 10 ++-- .../internal/test/mocks/ConsoleClient_mock.go | 47 +++++++++++++++++++ 8 files changed, 81 insertions(+), 14 deletions(-) diff --git a/controller/go.mod b/controller/go.mod index f901824afc..95265e2dd8 100644 --- a/controller/go.mod +++ b/controller/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.1 // Dependencies require ( github.com/Yamashou/gqlgenc v0.18.1 - github.com/pluralsh/console-client-go v0.11.1 + github.com/pluralsh/console-client-go v0.11.3 github.com/pluralsh/polly v0.1.7 github.com/samber/lo v1.39.0 github.com/spf13/pflag v1.0.5 diff --git a/controller/go.sum b/controller/go.sum index 4fc17a30fe..24396e5591 100644 --- a/controller/go.sum +++ b/controller/go.sum @@ -627,6 +627,8 @@ github.com/pluralsh/console-client-go v0.11.0 h1:Yk6d7V0tqz5TUHPwynbHrRKIcD2vJ25 github.com/pluralsh/console-client-go v0.11.0/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= github.com/pluralsh/console-client-go v0.11.1 h1:ljvJxCCyJSGCWFr9DYbfMxeJALu0BsUh8dwN3yQ6gvo= github.com/pluralsh/console-client-go v0.11.1/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= +github.com/pluralsh/console-client-go v0.11.3 h1:6HP0Tetebk0ZFRCALWjdy14Nzc8hci9/Jn0NuhQ2BKg= +github.com/pluralsh/console-client-go v0.11.3/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= github.com/pluralsh/polly v0.1.7 h1:MUuTb6rCUV1doisaFGC+iz+33ZPU4FZHOb/kFwSDkjw= github.com/pluralsh/polly v0.1.7/go.mod h1:Yo1/jcW+4xwhWG+ZJikZy4J4HJkMNPZ7sq5auL2c/tY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/controller/internal/client/console.go b/controller/internal/client/console.go index 28a611841c..598b1a8751 100644 --- a/controller/internal/client/console.go +++ b/controller/internal/client/console.go @@ -122,6 +122,7 @@ type ConsoleClient interface { GetProject(ctx context.Context, id, name *string) (*console.ProjectFragment, error) UpdateProject(ctx context.Context, id string, attributes console.ProjectAttributes) (*console.ProjectFragment, error) IsProjectExists(ctx context.Context, name string) (bool, error) + DeleteProject(ctx context.Context, id string) error UseCredentials(namespace string, credentialsCache credentials.NamespaceCredentialsCache) (string, error) } diff --git a/controller/internal/client/project.go b/controller/internal/client/project.go index 4494461dfd..db519b7c88 100644 --- a/controller/internal/client/project.go +++ b/controller/internal/client/project.go @@ -55,6 +55,11 @@ func (c *client) UpdateProject(ctx context.Context, id string, attributes consol return response.UpdateProject, nil } +func (c *client) DeleteProject(ctx context.Context, id string) error { + _, err := c.consoleClient.DeleteProject(ctx, id) + return err +} + func (c *client) IsProjectExists(ctx context.Context, name string) (bool, error) { scm, err := c.GetProject(ctx, nil, &name) if errors.IsNotFound(err) { diff --git a/controller/internal/controller/namespace_credentials_controller.go b/controller/internal/controller/namespace_credentials_controller.go index d99785b8e6..bac4f200cf 100644 --- a/controller/internal/controller/namespace_credentials_controller.go +++ b/controller/internal/controller/namespace_credentials_controller.go @@ -74,7 +74,7 @@ func (r *NamespaceCredentialsReconciler) Reconcile(ctx context.Context, req reco // Try to add namespace credentials to cache. if err := r.CredentialsCache.AddNamespaceCredentials(nc); err != nil { utils.MarkFalse(nc.SetCondition, v1alpha1.SynchronizedConditionType, v1alpha1.SynchronizedConditionReasonError, err.Error()) - return ctrl.Result{}, err + return requeue, nil } utils.MarkTrue(nc.SetCondition, v1alpha1.SynchronizedConditionType, v1alpha1.SynchronizedConditionReason, "") diff --git a/controller/internal/controller/project_controller.go b/controller/internal/controller/project_controller.go index fa61546f6f..65214fe472 100644 --- a/controller/internal/controller/project_controller.go +++ b/controller/internal/controller/project_controller.go @@ -72,7 +72,7 @@ func (in *ProjectReconciler) Reconcile(ctx context.Context, req reconcile.Reques utils.MarkCondition(project.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.ReadyConditionReason, "") // Handle proper resource deletion via finalizer - result := in.addOrRemoveFinalizer(project) + result := in.addOrRemoveFinalizer(ctx, project) if result != nil { return *result, nil } @@ -116,7 +116,7 @@ func (in *ProjectReconciler) Reconcile(ctx context.Context, req reconcile.Reques return requeue, nil } -func (in *ProjectReconciler) addOrRemoveFinalizer(project *v1alpha1.Project) *ctrl.Result { +func (in *ProjectReconciler) addOrRemoveFinalizer(ctx context.Context, project *v1alpha1.Project) *ctrl.Result { // If object is not being deleted and if it does not have our finalizer, // then lets add the finalizer. This is equivalent to registering our finalizer. if project.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(project, ProjectProtectionFinalizerName) { @@ -127,6 +127,22 @@ func (in *ProjectReconciler) addOrRemoveFinalizer(project *v1alpha1.Project) *ct // currently to delete project from Console API, so we simply detach // and only remove the CRD. if !project.ObjectMeta.DeletionTimestamp.IsZero() { + exists, err := in.ConsoleClient.IsProjectExists(ctx, project.Spec.Name) + if err != nil { + return &requeue + } + + // Remove Pipeline from Console API if it exists. + if exists { + if err := in.ConsoleClient.DeleteProject(ctx, project.Status.GetID()); err != nil { + // If it fails to delete the external dependency here, return with error + // so that it can be retried. + utils.MarkCondition(project.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error()) + return &ctrl.Result{} + } + + // project deletion is synchronous so can just fall back to removing the finalizer and reconciling + } // Stop reconciliation as the item is being deleted controllerutil.RemoveFinalizer(project, ProjectProtectionFinalizerName) return &ctrl.Result{} @@ -136,10 +152,6 @@ func (in *ProjectReconciler) addOrRemoveFinalizer(project *v1alpha1.Project) *ct } func (in *ProjectReconciler) isAlreadyExists(ctx context.Context, project *v1alpha1.Project) (bool, error) { - if project.Status.HasReadonlyCondition() { - return project.Status.IsReadonly(), nil - } - _, err := in.ConsoleClient.GetProject(ctx, nil, lo.ToPtr(project.ConsoleName())) if errors.IsNotFound(err) { return false, nil @@ -177,7 +189,7 @@ func (in *ProjectReconciler) handleExistingProject(ctx context.Context, project project.Status.ID = &apiProject.ID utils.MarkCondition(project.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionTrue, v1alpha1.SynchronizedConditionReason, "") - utils.MarkCondition(project.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionTrue, v1alpha1.ReadyConditionReason, "") + // utils.MarkCondition(project.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionTrue, v1alpha1.ReadyConditionReason, "") return requeue, nil } diff --git a/controller/internal/controller/service_account_controller.go b/controller/internal/controller/service_account_controller.go index dd8fa1375f..16f7089599 100644 --- a/controller/internal/controller/service_account_controller.go +++ b/controller/internal/controller/service_account_controller.go @@ -139,16 +139,16 @@ func (r *ServiceAccountReconciler) handleExistingServiceAccount(ctx context.Cont sa.Status.ID = &apiServiceAccount.ID - utils.MarkCondition(sa.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionTrue, v1alpha1.SynchronizedConditionReason, "") - utils.MarkCondition(sa.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionTrue, v1alpha1.ReadyConditionReason, "") + // utils.MarkCondition(sa.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionTrue, v1alpha1.SynchronizedConditionReason, "") + // utils.MarkCondition(sa.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionTrue, v1alpha1.ReadyConditionReason, "") return requeue, nil } func (r *ServiceAccountReconciler) isAlreadyExists(ctx context.Context, sa *v1alpha1.ServiceAccount) (bool, error) { - if sa.Status.HasReadonlyCondition() { - return sa.Status.IsReadonly(), nil - } + // if sa.Status.HasReadonlyCondition() { + // return sa.Status.IsReadonly(), nil + // } _, err := r.ConsoleClient.GetServiceAccount(ctx, sa.Spec.Email) if errors.IsNotFound(err) { diff --git a/controller/internal/test/mocks/ConsoleClient_mock.go b/controller/internal/test/mocks/ConsoleClient_mock.go index 9c6180e3a0..863f48afd1 100644 --- a/controller/internal/test/mocks/ConsoleClient_mock.go +++ b/controller/internal/test/mocks/ConsoleClient_mock.go @@ -1489,6 +1489,53 @@ func (_c *ConsoleClientMock_DeletePrAutomation_Call) RunAndReturn(run func(conte return _c } +// DeleteProject provides a mock function with given fields: ctx, id +func (_m *ConsoleClientMock) DeleteProject(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DeleteProject") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ConsoleClientMock_DeleteProject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteProject' +type ConsoleClientMock_DeleteProject_Call struct { + *mock.Call +} + +// DeleteProject is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *ConsoleClientMock_Expecter) DeleteProject(ctx interface{}, id interface{}) *ConsoleClientMock_DeleteProject_Call { + return &ConsoleClientMock_DeleteProject_Call{Call: _e.mock.On("DeleteProject", ctx, id)} +} + +func (_c *ConsoleClientMock_DeleteProject_Call) Run(run func(ctx context.Context, id string)) *ConsoleClientMock_DeleteProject_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ConsoleClientMock_DeleteProject_Call) Return(_a0 error) *ConsoleClientMock_DeleteProject_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ConsoleClientMock_DeleteProject_Call) RunAndReturn(run func(context.Context, string) error) *ConsoleClientMock_DeleteProject_Call { + _c.Call.Return(run) + return _c +} + // DeleteProvider provides a mock function with given fields: ctx, id func (_m *ConsoleClientMock) DeleteProvider(ctx context.Context, id string) error { ret := _m.Called(ctx, id)