github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/process/deprovisioning/btp_operator_cleanup.go (about)

     1  package deprovisioning
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/kyma-project/kyma-environment-broker/internal"
    10  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    11  	kebError "github.com/kyma-project/kyma-environment-broker/internal/error"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/process"
    13  	"github.com/kyma-project/kyma-environment-broker/internal/provisioner"
    14  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    15  	"github.com/sirupsen/logrus"
    16  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    17  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    18  	k8serrors2 "k8s.io/apimachinery/pkg/api/meta"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"k8s.io/apimachinery/pkg/runtime/schema"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  )
    25  
    26  const (
    27  	btpOperatorGroup           = "services.cloud.sap.com"
    28  	btpOperatorApiVer          = "v1"
    29  	btpOperatorServiceInstance = "ServiceInstance"
    30  	btpOperatorBinding         = "ServiceBinding"
    31  )
    32  
    33  type BTPOperatorCleanupStep struct {
    34  	operationManager  *process.DeprovisionOperationManager
    35  	provisionerClient provisioner.Client
    36  	k8sClientProvider func(kcfg string) (client.Client, error)
    37  }
    38  
    39  func NewBTPOperatorCleanupStep(os storage.Operations, provisionerClient provisioner.Client, k8sClientProvider func(kcfg string) (client.Client, error)) *BTPOperatorCleanupStep {
    40  	return &BTPOperatorCleanupStep{
    41  		operationManager:  process.NewDeprovisionOperationManager(os),
    42  		provisionerClient: provisionerClient,
    43  		k8sClientProvider: k8sClientProvider,
    44  	}
    45  }
    46  
    47  func (s *BTPOperatorCleanupStep) Name() string {
    48  	return "BTPOperator_Cleanup"
    49  }
    50  
    51  func (s *BTPOperatorCleanupStep) softDelete(operation internal.Operation, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    52  	k8sClient, err := s.getKubeClient(operation, log)
    53  	if err != nil || k8sClient == nil {
    54  		return s.retryOnError(operation, err, log, "failed to get kube client")
    55  	}
    56  	namespaces := corev1.NamespaceList{}
    57  	if err := k8sClient.List(context.Background(), &namespaces); err != nil {
    58  		return s.retryOnError(operation, err, log, "failed to list namespaces")
    59  	}
    60  
    61  	var errors []string
    62  	gvk := schema.GroupVersionKind{Group: btpOperatorGroup, Version: btpOperatorApiVer, Kind: btpOperatorBinding}
    63  	SBCrdExists, err := s.checkCRDExistence(k8sClient, gvk)
    64  	if err != nil {
    65  		return operation, 0, err
    66  	}
    67  	if SBCrdExists {
    68  		s.removeResources(k8sClient, gvk, namespaces, errors)
    69  	}
    70  
    71  	gvk.Kind = btpOperatorServiceInstance
    72  	SICrdExists, err := s.checkCRDExistence(k8sClient, gvk)
    73  	if err != nil {
    74  		return operation, 0, err
    75  	}
    76  	if SICrdExists {
    77  		s.removeResources(k8sClient, gvk, namespaces, errors)
    78  	}
    79  
    80  	if len(errors) != 0 {
    81  		return s.retryOnError(operation, fmt.Errorf(strings.Join(errors, ";")), log, "failed to cleanup")
    82  	}
    83  	return operation, 0, nil
    84  }
    85  
    86  func (s *BTPOperatorCleanupStep) Run(operation internal.Operation, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    87  	if operation.UserAgent == broker.AccountCleanupJob {
    88  		log.Info("executing soft delete cleanup for accountcleanup-job")
    89  		return s.softDelete(operation, log)
    90  	}
    91  	if !operation.Temporary {
    92  		log.Info("cleanup executed only for suspensions")
    93  		return operation, 0, nil
    94  	}
    95  	if operation.ProvisioningParameters.PlanID != broker.TrialPlanID {
    96  		log.Info("cleanup executed only for trial plan")
    97  		return operation, 0, nil
    98  	}
    99  	if operation.RuntimeID == "" {
   100  		log.Info("instance has been deprovisioned already")
   101  		return operation, 0, nil
   102  	}
   103  
   104  	kclient, err := s.getKubeClient(operation, log)
   105  	if err != nil {
   106  		return s.retryOnError(operation, err, log, "failed to get kube client")
   107  	}
   108  	if kclient == nil {
   109  		log.Infof("Skipping service instance and binding deletion")
   110  		return operation, 0, nil
   111  	}
   112  	if err := s.deleteServiceBindingsAndInstances(kclient, log); err != nil {
   113  		err = kebError.AsTemporaryError(err, "failed BTP operator resource cleanup")
   114  		return s.retryOnError(operation, err, log, "could not delete bindings and service instances")
   115  	}
   116  	return operation, 0, nil
   117  }
   118  
   119  func (s *BTPOperatorCleanupStep) deleteServiceBindingsAndInstances(k8sClient client.Client, log logrus.FieldLogger) error {
   120  	namespaces := corev1.NamespaceList{}
   121  	if err := k8sClient.List(context.Background(), &namespaces); err != nil {
   122  		return err
   123  	}
   124  	requeue := s.deleteResource(k8sClient, namespaces, schema.GroupVersionKind{Group: btpOperatorGroup, Version: btpOperatorApiVer, Kind: btpOperatorBinding}, log)
   125  	requeue = requeue || s.deleteResource(k8sClient, namespaces, schema.GroupVersionKind{Group: btpOperatorGroup, Version: btpOperatorApiVer, Kind: btpOperatorServiceInstance}, log)
   126  	if requeue {
   127  		return fmt.Errorf("waiting for resources to be deleted")
   128  	}
   129  	return nil
   130  }
   131  
   132  func (s *BTPOperatorCleanupStep) removeFinalizers(k8sClient client.Client, namespaces corev1.NamespaceList, gvk schema.GroupVersionKind) error {
   133  	listGvk := gvk
   134  	listGvk.Kind = gvk.Kind + "List"
   135  	var errors []string
   136  	for _, ns := range namespaces.Items {
   137  		list := &unstructured.UnstructuredList{}
   138  		list.SetGroupVersionKind(listGvk)
   139  		if err := k8sClient.List(context.Background(), list, client.InNamespace(ns.Name)); err != nil {
   140  			errors = append(errors, fmt.Sprintf("failed listing resource %v in namespace %v: %v", gvk, ns.Name, err))
   141  		}
   142  		for _, r := range list.Items {
   143  			r.SetFinalizers([]string{})
   144  			if err := k8sClient.Update(context.Background(), &r); err != nil {
   145  				errors = append(errors, fmt.Sprintf("failed remove finalizer for resource %v %v/%v: %v", gvk, r.GetNamespace(), r.GetName(), err))
   146  			}
   147  		}
   148  	}
   149  	if len(errors) != 0 {
   150  		return fmt.Errorf("failed to remove finalizers: %v", strings.Join(errors, ";"))
   151  	}
   152  	return nil
   153  }
   154  
   155  func (s *BTPOperatorCleanupStep) deleteResource(k8sClient client.Client, namespaces corev1.NamespaceList, gvk schema.GroupVersionKind, log logrus.FieldLogger) (requeue bool) {
   156  	listGvk := gvk
   157  	listGvk.Kind = gvk.Kind + "List"
   158  	stillExistingCount := 0
   159  	for _, ns := range namespaces.Items {
   160  		list := &unstructured.UnstructuredList{}
   161  		list.SetGroupVersionKind(listGvk)
   162  		if err := k8sClient.List(context.Background(), list, client.InNamespace(ns.Name)); err != nil {
   163  			log.Errorf("failed listing resource %v in namespace %v", gvk, ns.Name)
   164  			if k8serrors2.IsNoMatchError(err) {
   165  				// CRD doesn't exist anymore
   166  				return false
   167  			}
   168  			requeue = true
   169  		}
   170  		stillExistingCount += len(list.Items)
   171  	}
   172  	if stillExistingCount == 0 {
   173  		return
   174  	}
   175  	requeue = true
   176  	for _, ns := range namespaces.Items {
   177  		obj := &unstructured.Unstructured{}
   178  		obj.SetGroupVersionKind(gvk)
   179  		if err := k8sClient.DeleteAllOf(context.Background(), obj, client.InNamespace(ns.Name)); err != nil {
   180  			log.Errorf("failed deleting resources %v in namespace %v", gvk, ns.Name)
   181  		}
   182  	}
   183  	return
   184  }
   185  
   186  func (s *BTPOperatorCleanupStep) isNotFoundErr(err error) bool {
   187  	return strings.Contains(err.Error(), "not found")
   188  }
   189  
   190  func (s *BTPOperatorCleanupStep) retryOnError(op internal.Operation, err error, log logrus.FieldLogger, msg string) (internal.Operation, time.Duration, error) {
   191  	if err != nil {
   192  		// handleError returns retry period if it's retriable error and it's within timeout
   193  		op, retry, err2 := handleError(s.Name(), op, err, log, msg)
   194  		if retry != 0 {
   195  			return op, retry, err2
   196  		}
   197  		// when retry is 0, that means error has been retried defined number of times and as a fallback routine
   198  		// it was decided that KEB should try to remove finalizers once
   199  		s.attemptToRemoveFinalizers(op, log)
   200  		return op, retry, err2
   201  	}
   202  	return op, 0, nil
   203  }
   204  
   205  func (s *BTPOperatorCleanupStep) attemptToRemoveFinalizers(op internal.Operation, log logrus.FieldLogger) {
   206  	k8sClient, err := s.getKubeClient(op, log)
   207  	if err != nil {
   208  		log.Errorf("failed to get kube clients to remove finalizers", err)
   209  		return
   210  	}
   211  	if k8sClient == nil {
   212  		log.Info("Skipping removing finalizers")
   213  		return
   214  	}
   215  
   216  	namespaces := corev1.NamespaceList{}
   217  	if err := k8sClient.List(context.Background(), &namespaces); err != nil {
   218  		log.Errorf("failed to list namespaces to remove finalizers", err)
   219  		return
   220  	}
   221  	if err := s.removeFinalizers(k8sClient, namespaces, schema.GroupVersionKind{Group: btpOperatorGroup, Version: btpOperatorApiVer, Kind: btpOperatorBinding}); err != nil {
   222  		log.Errorf("failed to remove finalizers for bindings: %v", err)
   223  	}
   224  	if err := s.removeFinalizers(k8sClient, namespaces, schema.GroupVersionKind{Group: btpOperatorGroup, Version: btpOperatorApiVer, Kind: btpOperatorServiceInstance}); err != nil {
   225  		log.Errorf("failed to remove finalizers for instances: %v", err)
   226  	}
   227  }
   228  
   229  func (s *BTPOperatorCleanupStep) getKubeClient(operation internal.Operation, log logrus.FieldLogger) (client.Client, error) {
   230  	status, err := s.provisionerClient.RuntimeStatus(operation.ProvisioningParameters.ErsContext.GlobalAccountID, operation.RuntimeID)
   231  	if err != nil {
   232  		if s.isNotFoundErr(err) {
   233  			log.Info("Cannot get kubeconfig: instance not found in provisioner")
   234  			return nil, nil
   235  		}
   236  		return nil, err
   237  	}
   238  	if status.RuntimeConfiguration.Kubeconfig == nil {
   239  		return nil, kebError.NewTemporaryError("empty kubeconfig")
   240  	}
   241  	k := *status.RuntimeConfiguration.Kubeconfig
   242  	log.Infof("kubeconfig length: %v", len(k))
   243  	if len(k) < 10 {
   244  		return nil, kebError.NewTemporaryError("kubeconfig suspiciously small, requeuing")
   245  	}
   246  	cli, err := s.k8sClientProvider(k)
   247  	if err != nil {
   248  		return nil, kebError.AsTemporaryError(err, "failed to create k8s client from the kubeconfig")
   249  	}
   250  	return cli, nil
   251  }
   252  
   253  func (s *BTPOperatorCleanupStep) checkCRDExistence(k8sClient client.Client, gvk schema.GroupVersionKind) (bool, error) {
   254  	crdName := fmt.Sprintf("%ss.%s", strings.ToLower(gvk.Kind), gvk.Group)
   255  	crd := &apiextensionsv1.CustomResourceDefinition{}
   256  
   257  	if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: crdName}, crd); err != nil {
   258  		if k8serrors.IsNotFound(err) || k8serrors2.IsNoMatchError(err) {
   259  			return false, nil
   260  		} else {
   261  			return false, err
   262  		}
   263  	}
   264  	return true, nil
   265  }
   266  
   267  func (s *BTPOperatorCleanupStep) removeResources(k8sClient client.Client, gvk schema.GroupVersionKind, namespaces corev1.NamespaceList, errors []string) {
   268  	for _, ns := range namespaces.Items {
   269  		obj := &unstructured.Unstructured{}
   270  		obj.SetGroupVersionKind(gvk)
   271  		if err := k8sClient.DeleteAllOf(context.Background(), obj, client.InNamespace(ns.Name)); err != nil {
   272  			errors = append(errors, err.Error())
   273  		}
   274  	}
   275  	if err := s.removeFinalizers(k8sClient, namespaces, gvk); err != nil {
   276  		errors = append(errors, err.Error())
   277  	}
   278  }