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 }