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 }