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  }