github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/selfregmanager/selfreg_manager.go (about)

     1  package selfregmanager
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	urlpkg "net/url"
     9  	"path"
    10  	"strings"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    13  
    14  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    15  
    16  	"github.com/kyma-incubator/compass/components/director/pkg/config"
    17  
    18  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    19  
    20  	"github.com/kyma-incubator/compass/components/director/pkg/consumer"
    21  
    22  	"github.com/kyma-incubator/compass/components/director/pkg/httputils"
    23  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    24  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    25  	"github.com/pkg/errors"
    26  	"github.com/tidwall/gjson"
    27  )
    28  
    29  // ExternalSvcCaller is used to call external services with given authentication
    30  //
    31  //go:generate mockery --name=ExternalSvcCaller --output=automock --outpkg=automock --case=underscore --disable-version-string
    32  type ExternalSvcCaller interface {
    33  	Call(*http.Request) (*http.Response, error)
    34  }
    35  
    36  // ExternalSvcCallerProvider provides ExternalSvcCaller based on the provided SelfRegConfig and region
    37  //
    38  //go:generate mockery --name=ExternalSvcCallerProvider --output=automock --outpkg=automock --case=underscore --disable-version-string
    39  type ExternalSvcCallerProvider interface {
    40  	GetCaller(config.SelfRegConfig, string) (ExternalSvcCaller, error)
    41  }
    42  
    43  // RegionLabel label for the label repository indicating region
    44  const RegionLabel = "region"
    45  
    46  type selfRegisterManager struct {
    47  	cfg            config.SelfRegConfig
    48  	callerProvider ExternalSvcCallerProvider
    49  }
    50  
    51  // NewSelfRegisterManager creates a new SelfRegisterManager which is responsible for doing preparation/clean-up during
    52  // self-registration of runtimes configured with values from cfg.
    53  func NewSelfRegisterManager(cfg config.SelfRegConfig, provider ExternalSvcCallerProvider) (*selfRegisterManager, error) {
    54  	if err := cfg.PrepareConfiguration(); err != nil {
    55  		return nil, errors.Wrap(err, "while preparing self register manager configuration")
    56  	}
    57  	return &selfRegisterManager{cfg: cfg, callerProvider: provider}, nil
    58  }
    59  
    60  // IsSelfRegistrationFlow check if self registration flow is triggered
    61  func (s *selfRegisterManager) IsSelfRegistrationFlow(ctx context.Context, labels map[string]interface{}) (bool, error) {
    62  	consumerInfo, err := consumer.LoadFromContext(ctx)
    63  	if err != nil {
    64  		return false, errors.Wrapf(err, "while loading consumer")
    65  	}
    66  
    67  	if _, err := tenant.LoadFromContext(ctx); err == nil && consumerInfo.Flow.IsCertFlow() {
    68  		if _, exists := labels[s.cfg.SelfRegisterDistinguishLabelKey]; !exists {
    69  			return false, errors.Errorf("missing %q label", s.cfg.SelfRegisterDistinguishLabelKey)
    70  		}
    71  
    72  		return true, nil
    73  	}
    74  	return false, nil
    75  }
    76  
    77  // PrepareForSelfRegistration executes the prerequisite calls for self-registration in case the runtime
    78  // is being self-registered
    79  func (s *selfRegisterManager) PrepareForSelfRegistration(ctx context.Context, resourceType resource.Type, labels map[string]interface{}, id string, validate func() error) (map[string]interface{}, error) {
    80  	consumerInfo, err := consumer.LoadFromContext(ctx)
    81  	if err != nil {
    82  		return nil, errors.Wrapf(err, "while loading consumer")
    83  	}
    84  
    85  	if _, err := tenant.LoadFromContext(ctx); err == nil && consumerInfo.Flow.IsCertFlow() {
    86  		distinguishLabel, exists := labels[s.cfg.SelfRegisterDistinguishLabelKey]
    87  		if !exists {
    88  			if resourceType == resource.Runtime {
    89  				return labels, nil
    90  			}
    91  			return nil, errors.Errorf("missing %q label", s.cfg.SelfRegisterDistinguishLabelKey)
    92  		}
    93  
    94  		if err := validate(); err != nil {
    95  			return nil, err
    96  		}
    97  
    98  		if labels[RegionLabel] != nil {
    99  			return nil, errors.Errorf("providing %q label and value is forbidden", RegionLabel)
   100  		}
   101  
   102  		region := consumerInfo.Region
   103  		if region == "" {
   104  			return nil, errors.Errorf("missing %s value in consumer context", RegionLabel)
   105  		}
   106  
   107  		labels[RegionLabel] = region
   108  
   109  		instanceConfig, exists := s.cfg.RegionToInstanceConfig[region]
   110  		if !exists {
   111  			return nil, errors.Errorf("missing configuration for region: %s", region)
   112  		}
   113  
   114  		request, err := s.createSelfRegPrepRequest(id, consumerInfo.ConsumerID, instanceConfig.URL)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		caller, err := s.callerProvider.GetCaller(s.cfg, region)
   120  		if err != nil {
   121  			return nil, errors.Wrapf(err, "while getting caller")
   122  		}
   123  
   124  		response, err := caller.Call(request)
   125  		if err != nil {
   126  			return nil, errors.Wrapf(err, "while executing preparation of self registered resource")
   127  		}
   128  		defer httputils.Close(ctx, response.Body)
   129  
   130  		respBytes, err := io.ReadAll(response.Body)
   131  		if err != nil {
   132  			return nil, errors.Wrapf(err, "while reading response body")
   133  		}
   134  
   135  		if response.StatusCode != http.StatusCreated {
   136  			return nil, apperrors.NewCustomErrorWithCode(response.StatusCode, fmt.Sprintf("received unexpected status %d while preparing self-registered resource: %s", response.StatusCode, string(respBytes)))
   137  		}
   138  
   139  		selfRegLabelVal := gjson.GetBytes(respBytes, s.cfg.SelfRegisterResponseKey)
   140  		labels[s.cfg.SelfRegisterLabelKey] = selfRegLabelVal.Str
   141  
   142  		if resourceType == resource.Runtime {
   143  			saasAppName, exists := s.cfg.RegionToSaaSAppName[region]
   144  			if !exists {
   145  				return nil, errors.Errorf("missing SaaS application name for region: %q", region)
   146  			}
   147  
   148  			if saasAppName == "" {
   149  				return nil, errors.Errorf("SaaS application name for region: %q could not be empty", region)
   150  			}
   151  
   152  			labels[s.cfg.SaaSAppNameLabelKey] = saasAppName
   153  		}
   154  
   155  		log.C(ctx).Infof("Successfully executed prep for self-registration with distinguishing label value %s", str.CastOrEmpty(distinguishLabel))
   156  	}
   157  
   158  	return labels, nil
   159  }
   160  
   161  // CleanupSelfRegistration executes cleanup calls for self-registered runtimes
   162  func (s *selfRegisterManager) CleanupSelfRegistration(ctx context.Context, resourceID, region string) error {
   163  	consumerInfo, err := consumer.LoadFromContext(ctx)
   164  	if err != nil {
   165  		return errors.Wrapf(err, "while loading consumer")
   166  	}
   167  
   168  	if !consumerInfo.Flow.IsCertFlow() {
   169  		log.C(ctx).Infof("Not certificate flow, skipping clone deletion for resource %q", resourceID)
   170  		return nil
   171  	}
   172  
   173  	if resourceID == "" {
   174  		return nil
   175  	}
   176  
   177  	instanceConfig, exists := s.cfg.RegionToInstanceConfig[region]
   178  	if !exists {
   179  		return errors.Errorf("missing configuration for region: %s", region)
   180  	}
   181  
   182  	request, err := s.createSelfRegDelRequest(resourceID, instanceConfig.URL)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	caller, err := s.callerProvider.GetCaller(s.cfg, region)
   188  	if err != nil {
   189  		return errors.Wrapf(err, "while getting caller")
   190  	}
   191  	resp, err := caller.Call(request)
   192  	if err != nil {
   193  		return errors.Wrapf(err, "while executing cleanup of self-registered resource with id %q", resourceID)
   194  	}
   195  
   196  	if resp.StatusCode != http.StatusOK {
   197  		return errors.Errorf("received unexpected status code %d while cleaning up self-registered resource with id %q", resp.StatusCode, resourceID)
   198  	}
   199  
   200  	log.C(ctx).Infof("Successfully executed clean-up self-registered resource with id %q", resourceID)
   201  	return nil
   202  }
   203  
   204  // GetSelfRegDistinguishingLabelKey returns the label key to be used in order to determine whether a resource
   205  // is being self-registered.
   206  func (s *selfRegisterManager) GetSelfRegDistinguishingLabelKey() string {
   207  	return s.cfg.SelfRegisterDistinguishLabelKey
   208  }
   209  
   210  func (s *selfRegisterManager) createSelfRegPrepRequest(id, tenant, targetURL string) (*http.Request, error) {
   211  	selfRegLabelVal := s.cfg.SelfRegisterLabelValuePrefix + id
   212  	url, err := urlpkg.Parse(targetURL)
   213  	if err != nil {
   214  		return nil, errors.Wrapf(err, "while creating url for preparation of self-registered resource")
   215  	}
   216  	url.Path = path.Join(url.Path, s.cfg.SelfRegisterPath)
   217  	q := url.Query()
   218  	q.Add(s.cfg.SelfRegisterNameQueryParam, selfRegLabelVal)
   219  	q.Add(s.cfg.SelfRegisterTenantQueryParam, tenant)
   220  	url.RawQuery = q.Encode()
   221  
   222  	request, err := http.NewRequest(http.MethodPost, url.String(), strings.NewReader(fmt.Sprintf(s.cfg.SelfRegisterRequestBodyPattern, selfRegLabelVal)))
   223  	if err != nil {
   224  		return nil, errors.Wrapf(err, "while preparing request for self-registered resource")
   225  	}
   226  	request.Header.Set("Content-Type", "application/json")
   227  
   228  	return request, nil
   229  }
   230  
   231  func (s *selfRegisterManager) createSelfRegDelRequest(resourceID, targetURL string) (*http.Request, error) {
   232  	selfRegLabelVal := s.cfg.SelfRegisterLabelValuePrefix + resourceID
   233  	url, err := urlpkg.Parse(targetURL)
   234  	if err != nil {
   235  		return nil, errors.Wrapf(err, "while creating url for cleanup of self-registered resource")
   236  	}
   237  	url.Path = path.Join(url.Path, s.cfg.SelfRegisterPath)
   238  	url.Path = path.Join(url.Path, selfRegLabelVal)
   239  
   240  	request, err := http.NewRequest(http.MethodDelete, url.String(), nil)
   241  	if err != nil {
   242  		return nil, errors.Wrapf(err, "while preparing request for cleanup of self-registered resource")
   243  	}
   244  	request.Header.Set("Content-Type", "application/json")
   245  
   246  	return request, nil
   247  }