github.com/kyma-project/kyma-environment-broker@v0.0.1/cmd/subscriptioncleanup/job/cleaner.go (about) 1 package job 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/kyma-project/kyma-environment-broker/cmd/subscriptioncleanup/cloudprovider" 8 "github.com/kyma-project/kyma-environment-broker/cmd/subscriptioncleanup/model" 9 "github.com/kyma-project/kyma-environment-broker/common/gardener" 10 "github.com/sirupsen/logrus" 11 apiv1 "k8s.io/api/core/v1" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 "k8s.io/client-go/dynamic" 15 "k8s.io/client-go/kubernetes" 16 ) 17 18 type Type string 19 20 type Cleaner interface { 21 Do() error 22 } 23 24 func NewCleaner(context context.Context, 25 kubernetesInterface kubernetes.Interface, 26 secretBindingsClient dynamic.ResourceInterface, 27 shootClient dynamic.ResourceInterface, 28 providerFactory cloudprovider.ProviderFactory) Cleaner { 29 30 return &cleaner{ 31 kubernetesInterface: kubernetesInterface, 32 secretBindingsClient: secretBindingsClient, 33 providerFactory: providerFactory, 34 shootClient: shootClient, 35 context: context, 36 } 37 } 38 39 type cleaner struct { 40 kubernetesInterface kubernetes.Interface 41 secretBindingsClient dynamic.ResourceInterface 42 providerFactory cloudprovider.ProviderFactory 43 shootClient dynamic.ResourceInterface 44 context context.Context 45 } 46 47 func (p *cleaner) Do() error { 48 logrus.Info("Started releasing resources") 49 secretBindings, err := p.getSecretBindingsToRelease() 50 if err != nil { 51 return err 52 } 53 for _, secretBinding := range secretBindings { 54 canRelease, err := p.checkIfSecretCanBeReleased(secretBinding) 55 if err != nil { 56 logrus.Errorf("Failed to list shoots for '%s' secret binding: %s", secretBinding.GetName(), err.Error()) 57 continue 58 } 59 60 if !canRelease { 61 logrus.Warnf("Cannot release secret binding: %s. Still in use", secretBinding.GetName()) 62 continue 63 } 64 65 err = p.releaseResources(secretBinding) 66 if err != nil { 67 logrus.Errorf("Failed to release resources for '%s' secret binding: %s", secretBinding.GetName(), err.Error()) 68 continue 69 } 70 err = p.returnSecretBindingToThePool(secretBinding) 71 if err != nil { 72 logrus.Errorf("Failed returning '%s' secret binding to the pool: %s", secretBinding.GetName(), err.Error()) 73 continue 74 } 75 logrus.Infof("Resources released for '%s' secret binding", secretBinding.GetName()) 76 } 77 78 logrus.Info("Finished releasing resources") 79 return nil 80 } 81 82 func (p *cleaner) releaseResources(secretBinding unstructured.Unstructured) error { 83 hyperscalerType, err := model.NewHyperscalerType(secretBinding.GetLabels()["hyperscalerType"]) 84 if err != nil { 85 return fmt.Errorf("starting releasing resources: %w", err) 86 } 87 88 secret, err := p.getBoundSecret(secretBinding) 89 if err != nil { 90 return fmt.Errorf("getting referenced secret: %w", err) 91 } 92 93 cleaner, err := p.providerFactory.New(hyperscalerType, secret.Data) 94 if err != nil { 95 return fmt.Errorf("initializing cloud provider cleaner: %w", err) 96 } 97 98 return cleaner.Do() 99 } 100 101 func (p *cleaner) getBoundSecret(sb unstructured.Unstructured) (*apiv1.Secret, error) { 102 secretBinding := gardener.SecretBinding{sb} 103 secret, err := p.kubernetesInterface.CoreV1(). 104 Secrets(secretBinding.GetSecretRefNamespace()). 105 Get(p.context, secretBinding.GetSecretRefName(), metav1.GetOptions{}) 106 if err != nil { 107 return nil, fmt.Errorf("getting %s/%s secret: %w", secretBinding.GetSecretRefNamespace(), 108 secretBinding.GetSecretRefName(), err) 109 } 110 return secret, nil 111 } 112 113 func (p *cleaner) returnSecretBindingToThePool(secretBinding unstructured.Unstructured) error { 114 sb, err := p.secretBindingsClient.Get(p.context, secretBinding.GetName(), metav1.GetOptions{}) 115 if err != nil { 116 return err 117 } 118 119 l := sb.GetLabels() 120 delete(l, "dirty") 121 delete(l, "tenantName") 122 sb.SetLabels(l) 123 124 _, err = p.secretBindingsClient.Update(p.context, sb, metav1.UpdateOptions{}) 125 if err != nil { 126 return fmt.Errorf("failed to return secret binding to the hyperscaler account pool: %w", err) 127 } 128 129 return nil 130 } 131 132 func (p *cleaner) getSecretBindingsToRelease() ([]unstructured.Unstructured, error) { 133 labelSelector := fmt.Sprintf("dirty=true") 134 135 return getSecretBindings(p.context, p.secretBindingsClient, labelSelector) 136 } 137 138 // Checks if there are no clusters tied to the secret binding 139 func (p *cleaner) checkIfSecretCanBeReleased(binding unstructured.Unstructured) (bool, error) { 140 list, err := p.shootClient.List(p.context, metav1.ListOptions{}) 141 if err != nil { 142 return false, fmt.Errorf("failed to list shoots: %w", err) 143 } 144 145 for _, sh := range list.Items { 146 shoot := gardener.Shoot{sh} 147 if shoot.GetSpecSecretBindingName() == binding.GetName() { 148 return false, nil 149 } 150 } 151 152 return true, nil 153 } 154 155 func getSecretBindings(ctx context.Context, secretBindingsClient dynamic.ResourceInterface, labelSelector string) ([]unstructured.Unstructured, error) { 156 secrets, err := secretBindingsClient.List(ctx, metav1.ListOptions{ 157 LabelSelector: labelSelector, 158 }) 159 if err != nil { 160 return nil, fmt.Errorf("listing secrets bindings for LabelSelector: %s: %w", labelSelector, err) 161 } 162 163 return secrets.Items, nil 164 }