Skip to content

Commit

Permalink
Merge pull request #751 from KevFan/THREESCALE-7052
Browse files Browse the repository at this point in the history
THREESCALE-7055 - feat: delete developer user
  • Loading branch information
eguzki committed Jun 1, 2022
2 parents 1b2b6cf + bd3283c commit b5eb7b9
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 9 deletions.
117 changes: 114 additions & 3 deletions controllers/capabilities/developeruser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import (
"context"
"encoding/json"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"

threescaleapi "github.com/3scale/3scale-porta-go-client/client"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1"
controllerhelper "github.com/3scale/3scale-operator/pkg/controller/helper"
Expand All @@ -29,13 +34,14 @@ import (

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const developerUserFinalizer = "developeruser.capabilities.3scale.net/finalizer"

// DeveloperUserReconciler reconciles a DeveloperUser object
type DeveloperUserReconciler struct {
*reconcilers.BaseReconciler
Expand All @@ -55,7 +61,7 @@ func (r *DeveloperUserReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
developerUserCR := &capabilitiesv1beta1.DeveloperUser{}
err := r.Client().Get(context.TODO(), req.NamespacedName, developerUserCR)
if err != nil {
if errors.IsNotFound(err) {
if apierrors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
Expand All @@ -74,12 +80,67 @@ func (r *DeveloperUserReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
reqLogger.V(1).Info(string(jsonData))
}

// DeveloperUser has been marked for deletion
if developerUserCR.GetDeletionTimestamp() != nil && controllerutil.ContainsFinalizer(developerUserCR, developerUserFinalizer) {
err = r.removeDeveloperUserFrom3scale(developerUserCR)
if err != nil {
r.EventRecorder().Eventf(developerUserCR, corev1.EventTypeWarning, "Failed to delete developer user", "%v", err)

// Update status with err
statusResult, statusUpdateErr := NewDeveloperUserStatusReconciler(r.BaseReconciler, developerUserCR, nil, "", nil, err).Reconcile()
if statusUpdateErr != nil {
return ctrl.Result{}, fmt.Errorf("Failed to update developers user status: %w", statusUpdateErr)
}

if statusResult.Requeue {
return statusResult, nil
}

return ctrl.Result{}, err
}

controllerutil.RemoveFinalizer(developerUserCR, developerUserFinalizer)
err = r.UpdateResource(developerUserCR)
if err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// Ignore deleted resource, this can happen when foregroundDeletion is enabled
// https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#foreground-cascading-deletion
if developerUserCR.DeletionTimestamp != nil {
return ctrl.Result{}, nil
}

if !controllerutil.ContainsFinalizer(developerUserCR, developerUserFinalizer) {
controllerutil.AddFinalizer(developerUserCR, developerUserFinalizer)
err = r.UpdateResource(developerUserCR)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

// Retrieve ownersReference of DeveloperAccount CR that owns the DeveloperUser CR
developerAccountCR, err := r.retrieveDevelopAccountCR(developerUserCR)
if err != nil {
return ctrl.Result{}, err
}

// If DeveloperAccount CR is found, set it's ownersReference as ownerReference in the DeveloperUser CR
if developerAccountCR != nil {
updated, err := controllerhelper.SetOwnersReferenceByObjectAndMeta(developerUserCR, r.Client(), developerAccountCR.ObjectMeta, developerAccountCR.TypeMeta)
if err != nil {
return ctrl.Result{}, err
}

if updated {
return ctrl.Result{Requeue: true}, nil
}
}

statusReconciler, reconcileErr := r.reconcileSpec(developerUserCR, reqLogger)
statusResult, statusUpdateErr := statusReconciler.Reconcile()
if statusUpdateErr != nil {
Expand Down Expand Up @@ -168,7 +229,7 @@ func (r *DeveloperUserReconciler) findParentAccount(userCR *capabilitiesv1beta1.
devAccountCR := &capabilitiesv1beta1.DeveloperAccount{}
devAccountKey := types.NamespacedName{Name: userCR.Spec.DeveloperAccountRef.Name, Namespace: userCR.Namespace}
if err := r.Client().Get(r.Context(), devAccountKey, devAccountCR); err != nil {
if errors.IsNotFound(err) {
if apierrors.IsNotFound(err) {
return nil, &helper.SpecFieldError{
ErrorType: helper.OrphanError,
FieldErrorList: field.ErrorList{
Expand Down Expand Up @@ -208,6 +269,56 @@ func (r *DeveloperUserReconciler) findParentAccount(userCR *capabilitiesv1beta1.
return devAccountCR, nil
}

func (r *DeveloperUserReconciler) removeDeveloperUserFrom3scale(developerUser *capabilitiesv1beta1.DeveloperUser) error {
logger := r.Logger().WithValues("developerUser", client.ObjectKey{Name: developerUser.Name, Namespace: developerUser.Namespace})

// Attempt to remove developerUser only if developerUser.Status.ID is present
if developerUser.Status.ID == nil {
logger.Info("could not remove developerUser because ID is missing in status")
return nil
}

if developerUser.Status.AccountID == nil {
logger.Info("could not remove developerUser because Account ID is missing in status")
return nil
}

providerAccount, err := controllerhelper.LookupProviderAccount(r.Client(), developerUser.Namespace, developerUser.Spec.ProviderAccountRef, logger)
if err != nil {
if apierrors.IsNotFound(err) {
logger.Info("developer user not deleted from 3scale, provider account not found")
return nil
}
return err
}

threescaleAPIClient, err := controllerhelper.PortaClient(providerAccount)
if err != nil {
return err
}

err = threescaleAPIClient.DeleteDeveloperUser(*developerUser.Status.AccountID, *developerUser.Status.ID)
if err != nil && !threescaleapi.IsNotFound(err) {
return err
}

return nil
}

func (r *DeveloperUserReconciler) retrieveDevelopAccountCR(developerUser *capabilitiesv1beta1.DeveloperUser) (*capabilitiesv1beta1.DeveloperAccount, error) {
developerAccount := &capabilitiesv1beta1.DeveloperAccount{}

if err := r.Client().Get(context.TODO(), client.ObjectKey{Namespace: developerUser.Namespace, Name: developerUser.Spec.DeveloperAccountRef.Name}, developerAccount); err != nil {
if !apierrors.IsNotFound(err) {
return nil, err
}

return nil, nil
}

return developerAccount, nil
}

func (r *DeveloperUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&capabilitiesv1beta1.DeveloperUser{}).
Expand Down
8 changes: 8 additions & 0 deletions doc/developeruser-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [Provider Account Reference](#provider-account-reference)
* [DeveloperUserStatus](#developeruserstatus)
* [ConditionSpec](#conditionspec)
* [Supported Actions](#Supported Actions)

Generated using [github-markdown-toc](https://github.com/ekalinin/github-markdown-toc)

Expand Down Expand Up @@ -132,3 +133,10 @@ Each element of the Condition array has the following fields:
| Reason | `reason` | string | Condition state reason |
| Message | `message` | string | Condition state description |
| LastTransitionTime | `lastTransitionTime` | timestamp | Last transition timestap |


## Supported Actions
* Create - creating the CR will create the developer user in the associated account and tenant
* Delete - deleting the CR will delete the developer user in the associated account and tenant
* Note: Due to a 3scale limitation, if the user is the last admin of the account, the CR will be prevented from
deleting successfully
22 changes: 16 additions & 6 deletions pkg/controller/helper/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package helper

import (
"context"

capabilitiesv1alpha1 "github.com/3scale/3scale-operator/apis/capabilities/v1alpha1"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -52,18 +51,29 @@ func RetrieveTenantCR(providerAccount *ProviderAccount, client k8sclient.Client,
}

/*
SetOwnersReference sets ownersReference in given object
SetOwnersReference sets ownersReference in given object for Tenant
- object
- k8client
- tenantCR
*/
func SetOwnersReference(object controllerutil.Object, client k8sclient.Client, tenantCR *capabilitiesv1alpha1.Tenant) (bool, error) {
return SetOwnersReferenceByObjectAndMeta(object, client, tenantCR.ObjectMeta, tenantCR.TypeMeta)
}

/*
SetOwnersReferenceByObjectAndMeta sets ownersReference in given object using ObjectMeta and TypeMeta
- object
- k8client
- objectMeta
- typeMeta
*/
func SetOwnersReferenceByObjectAndMeta(object controllerutil.Object, client k8sclient.Client, objectMeta metav1.ObjectMeta, typeMeta metav1.TypeMeta) (bool, error) {
ownerReference := []metav1.OwnerReference{
{
APIVersion: tenantCR.APIVersion,
Kind: tenantCR.Kind,
Name: tenantCR.Name,
UID: tenantCR.UID,
APIVersion: typeMeta.APIVersion,
Kind: typeMeta.Kind,
Name: objectMeta.Name,
UID: objectMeta.UID,
},
}

Expand Down

0 comments on commit b5eb7b9

Please sign in to comment.