github.com/kyma-project/kyma-environment-broker@v0.0.1/common/hyperscaler/account_pool.go (about) 1 package hyperscaler 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/kyma-project/kyma-environment-broker/common/gardener" 9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/client-go/dynamic" 12 ) 13 14 type Type string 15 16 const ( 17 GCP Type = "gcp" 18 Azure Type = "azure" 19 AWS Type = "aws" 20 Openstack Type = "openstack" 21 ) 22 23 type AccountPool interface { 24 CredentialsSecretBinding(hyperscalerType Type, tenantName string, euAccess bool) (*gardener.SecretBinding, error) 25 MarkSecretBindingAsDirty(hyperscalerType Type, tenantName string, euAccess bool) error 26 IsSecretBindingUsed(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) 27 IsSecretBindingDirty(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) 28 IsSecretBindingInternal(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) 29 } 30 31 func NewAccountPool(gardenerClient dynamic.Interface, gardenerNamespace string) AccountPool { 32 return &secretBindingsAccountPool{ 33 gardenerClient: gardenerClient, 34 gardenerNS: gardenerNamespace, 35 } 36 } 37 38 type secretBindingsAccountPool struct { 39 gardenerClient dynamic.Interface 40 gardenerNS string 41 mux sync.Mutex 42 } 43 44 func (p *secretBindingsAccountPool) IsSecretBindingInternal(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) { 45 labelSelector := fmt.Sprintf("internal=true, tenantName=%s,hyperscalerType=%s", tenantName, hyperscalerType) 46 labelSelector = addEuAccessSelector(labelSelector, euAccess) 47 secretBinding, err := p.getSecretBinding(labelSelector) 48 if err != nil { 49 return false, fmt.Errorf("looking for a secret binding used by the tenant %s and hyperscaler %s: %w", tenantName, hyperscalerType, err) 50 } 51 52 if secretBinding != nil { 53 return true, nil 54 } 55 return false, nil 56 } 57 58 func (p *secretBindingsAccountPool) IsSecretBindingDirty(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) { 59 labelSelector := fmt.Sprintf("shared!=true, dirty=true, tenantName=%s,hyperscalerType=%s", tenantName, hyperscalerType) 60 labelSelector = addEuAccessSelector(labelSelector, euAccess) 61 secretBinding, err := p.getSecretBinding(labelSelector) 62 if err != nil { 63 return false, fmt.Errorf("looking for a secret binding used by the tenant %s and hyperscaler %s: %w", tenantName, hyperscalerType, err) 64 } 65 66 if secretBinding != nil { 67 return true, nil 68 } 69 return false, nil 70 } 71 72 func (p *secretBindingsAccountPool) MarkSecretBindingAsDirty(hyperscalerType Type, tenantName string, euAccess bool) error { 73 p.mux.Lock() 74 defer p.mux.Unlock() 75 76 labelSelector := fmt.Sprintf("shared!=true, tenantName=%s,hyperscalerType=%s", tenantName, hyperscalerType) 77 labelSelector = addEuAccessSelector(labelSelector, euAccess) 78 secretBinding, err := p.getSecretBinding(labelSelector) 79 if err != nil { 80 return fmt.Errorf("marking secret binding as dirty: failed to find secret binding used by the tenant %s and"+" hyperscaler %s: %w", tenantName, hyperscalerType, err) 81 } 82 // if there is no matching secret - do nothing 83 if secretBinding == nil { 84 return nil 85 } 86 87 labels := secretBinding.GetLabels() 88 labels["dirty"] = "true" 89 secretBinding.SetLabels(labels) 90 91 _, err = p.gardenerClient.Resource(gardener.SecretBindingResource).Namespace(p.gardenerNS).Update(context.Background(), &secretBinding.Unstructured, v1.UpdateOptions{}) 92 if err != nil { 93 return fmt.Errorf("marking secret binding as dirty: failed to update secret binding for tenant: %s and hyperscaler: %s: %w", tenantName, hyperscalerType, err) 94 95 } 96 return nil 97 } 98 99 func (p *secretBindingsAccountPool) IsSecretBindingUsed(hyperscalerType Type, tenantName string, euAccess bool) (bool, error) { 100 labelSelector := fmt.Sprintf("tenantName=%s,hyperscalerType=%s", tenantName, hyperscalerType) 101 labelSelector = addEuAccessSelector(labelSelector, euAccess) 102 secretBinding, err := p.getSecretBinding(labelSelector) 103 if err != nil { 104 return false, fmt.Errorf("counting subscription usage: could not find secret binding used by the tenant %s and hyperscaler %s: %w", tenantName, hyperscalerType, err) 105 } 106 // if there is no matching secret, that's ok (maybe it was not used, for example the step was not run) 107 if secretBinding == nil { 108 return false, nil 109 } 110 111 shootlist, err := p.gardenerClient.Resource(gardener.ShootResource).Namespace(p.gardenerNS).List(context.Background(), metav1.ListOptions{}) 112 if err != nil { 113 return false, fmt.Errorf("listing Gardener shoots: %w", err) 114 } 115 116 for _, shoot := range shootlist.Items { 117 sh := gardener.Shoot{shoot} 118 if sh.GetSpecSecretBindingName() == secretBinding.GetName() { 119 return true, nil 120 } 121 } 122 123 return false, nil 124 } 125 126 func (p *secretBindingsAccountPool) CredentialsSecretBinding(hyperscalerType Type, tenantName string, euAccess bool) (*gardener.SecretBinding, error) { 127 labelSelector := fmt.Sprintf("tenantName=%s, hyperscalerType=%s, !dirty", tenantName, hyperscalerType) 128 labelSelector = addEuAccessSelector(labelSelector, euAccess) 129 secretBinding, err := p.getSecretBinding(labelSelector) 130 if err != nil { 131 return nil, fmt.Errorf("getting secret binding: %w", err) 132 } 133 if secretBinding != nil { 134 return secretBinding, nil 135 } 136 137 // lock so that only one thread can fetch an unassigned secret binding and assign it 138 // (update secret binding with tenantName) 139 p.mux.Lock() 140 defer p.mux.Unlock() 141 142 labelSelector = fmt.Sprintf("shared!=true, !tenantName, !dirty, hyperscalerType=%s", hyperscalerType) 143 labelSelector = addEuAccessSelector(labelSelector, euAccess) 144 secretBinding, err = p.getSecretBinding(labelSelector) 145 if err != nil { 146 return nil, fmt.Errorf("getting secret binding: %w", err) 147 } 148 if secretBinding == nil { 149 return nil, fmt.Errorf("failed to find unassigned secret binding for hyperscalerType: %s", hyperscalerType) 150 } 151 152 labels := secretBinding.GetLabels() 153 labels["tenantName"] = tenantName 154 secretBinding.SetLabels(labels) 155 updatedSecretBinding, err := p.gardenerClient.Resource(gardener.SecretBindingResource).Namespace(p.gardenerNS).Update(context.Background(), &secretBinding.Unstructured, v1.UpdateOptions{}) 156 if err != nil { 157 return nil, fmt.Errorf("updating secret binding with tenantName: %s: %w", tenantName, err) 158 } 159 160 return &gardener.SecretBinding{*updatedSecretBinding}, nil 161 } 162 163 func (p *secretBindingsAccountPool) getSecretBinding(labelSelector string) (*gardener.SecretBinding, error) { 164 secretBindings, err := p.gardenerClient.Resource(gardener.SecretBindingResource).Namespace(p.gardenerNS).List(context.Background(), metav1.ListOptions{ 165 LabelSelector: labelSelector, 166 }) 167 if err != nil { 168 return nil, fmt.Errorf("listing secret bindings for LabelSelector: %s: %w", labelSelector, err) 169 } 170 171 if secretBindings != nil && len(secretBindings.Items) > 0 { 172 return &gardener.SecretBinding{secretBindings.Items[0]}, nil 173 } 174 return nil, nil 175 } 176 177 func addEuAccessSelector(selector string, euAccess bool) string { 178 if euAccess { 179 return selector + ", euAccess=true" 180 } else { 181 return selector + ", !euAccess" 182 } 183 }