github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/subscription/service.go (about) 1 package subscription 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/kyma-incubator/compass/components/director/internal/repo" 9 10 "github.com/kyma-incubator/compass/components/director/pkg/str" 11 12 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 13 "github.com/kyma-incubator/compass/components/director/pkg/inputvalidation" 14 "github.com/kyma-incubator/compass/components/director/pkg/log" 15 16 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 17 "github.com/kyma-incubator/compass/components/director/internal/labelfilter" 18 "github.com/kyma-incubator/compass/components/director/internal/model" 19 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 20 "github.com/kyma-incubator/compass/components/director/pkg/resource" 21 "github.com/pkg/errors" 22 ) 23 24 // Config is configuration for the tenant subscription flow 25 type Config struct { 26 ProviderLabelKey string `envconfig:"APP_SUBSCRIPTION_PROVIDER_LABEL_KEY,default=subscriptionProviderId"` 27 ConsumerSubaccountLabelKey string `envconfig:"APP_CONSUMER_SUBACCOUNT_LABEL_KEY,default=global_subaccount_id"` 28 SubscriptionLabelKey string `envconfig:"APP_SUBSCRIPTION_LABEL_KEY,default=subscription"` 29 RuntimeTypeLabelKey string `envconfig:"APP_RUNTIME_TYPE_LABEL_KEY,default=runtimeType"` 30 } 31 32 const ( 33 // SubdomainLabelKey is the key of the tenant label for subdomain. 34 SubdomainLabelKey = "subdomain" 35 // RegionPrefix a prefix to be trimmed from the region placeholder value when creating an app from template 36 RegionPrefix = "cf-" 37 // InstancesLabelKey is the key of the instances label, that stores the number of created instances. 38 InstancesLabelKey = "instances" 39 // DefaultNumberOfInstancesForAlreadySubscribedTenant is the default number of instances set to instances label value for already subscribed tenant 40 DefaultNumberOfInstancesForAlreadySubscribedTenant = 2 41 ) 42 43 // RuntimeService is responsible for Runtime operations 44 // 45 //go:generate mockery --name=RuntimeService --output=automock --outpkg=automock --case=underscore --disable-version-string 46 type RuntimeService interface { 47 GetByFiltersGlobal(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error) 48 GetByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error) 49 } 50 51 // RuntimeCtxService provide functionality to interact with the runtime contexts(create, list, delete). 52 // 53 //go:generate mockery --name=RuntimeCtxService --output=automock --outpkg=automock --case=underscore 54 type RuntimeCtxService interface { 55 Create(ctx context.Context, in model.RuntimeContextInput) (string, error) 56 Delete(ctx context.Context, id string) error 57 ListByFilter(ctx context.Context, runtimeID string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error) 58 } 59 60 // TenantService provides functionality for retrieving, and creating tenants. 61 // 62 //go:generate mockery --name=TenantService --output=automock --outpkg=automock --case=underscore --unroll-variadic=False --disable-version-string 63 type TenantService interface { 64 GetLowestOwnerForResource(ctx context.Context, resourceType resource.Type, objectID string) (string, error) 65 GetInternalTenant(ctx context.Context, externalTenant string) (string, error) 66 } 67 68 // LabelService is responsible updating already existing labels, and their label definitions. 69 // 70 //go:generate mockery --name=LabelService --output=automock --outpkg=automock --case=underscore --disable-version-string 71 type LabelService interface { 72 GetLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) (*model.Label, error) 73 CreateLabel(ctx context.Context, tenant, id string, labelInput *model.LabelInput) error 74 UpdateLabel(ctx context.Context, tenant, id string, labelInput *model.LabelInput) error 75 UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error 76 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 77 } 78 79 //go:generate mockery --exported --name=uidService --output=automock --outpkg=automock --case=underscore --disable-version-string 80 type uidService interface { 81 Generate() string 82 } 83 84 // ApplicationTemplateService is responsible for Application Template operations 85 // 86 //go:generate mockery --name=ApplicationTemplateService --output=automock --outpkg=automock --case=underscore --disable-version-string 87 type ApplicationTemplateService interface { 88 Exists(ctx context.Context, id string) (bool, error) 89 GetByFilters(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.ApplicationTemplate, error) 90 PrepareApplicationCreateInputJSON(appTemplate *model.ApplicationTemplate, values model.ApplicationFromTemplateInputValues) (string, error) 91 } 92 93 // ApplicationTemplateConverter missing godoc 94 // 95 //go:generate mockery --name=ApplicationTemplateConverter --output=automock --outpkg=automock --case=underscore --disable-version-string 96 type ApplicationTemplateConverter interface { 97 ApplicationFromTemplateInputFromGraphQL(appTemplate *model.ApplicationTemplate, in graphql.ApplicationFromTemplateInput) (model.ApplicationFromTemplateInput, error) 98 } 99 100 // ApplicationConverter is converting graphql and model Applications 101 // 102 //go:generate mockery --name=ApplicationConverter --output=automock --outpkg=automock --case=underscore --disable-version-string 103 type ApplicationConverter interface { 104 ToGraphQL(in *model.Application) *graphql.Application 105 CreateRegisterInputJSONToGQL(in string) (graphql.ApplicationRegisterInput, error) 106 CreateInputFromGraphQL(ctx context.Context, in graphql.ApplicationRegisterInput) (model.ApplicationRegisterInput, error) 107 } 108 109 // ApplicationService is responsible for Application operations 110 // 111 //go:generate mockery --name=ApplicationService --output=automock --outpkg=automock --case=underscore --disable-version-string 112 type ApplicationService interface { 113 CreateFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) (string, error) 114 ListAll(ctx context.Context) ([]*model.Application, error) 115 Delete(ctx context.Context, id string) error 116 } 117 118 type service struct { 119 runtimeSvc RuntimeService 120 runtimeCtxSvc RuntimeCtxService 121 tenantSvc TenantService 122 labelSvc LabelService 123 appTemplateSvc ApplicationTemplateService 124 appConv ApplicationConverter 125 appTemplateConv ApplicationTemplateConverter 126 appSvc ApplicationService 127 uidSvc uidService 128 consumerSubaccountLabelKey string 129 subscriptionLabelKey string 130 runtimeTypeLabelKey string 131 subscriptionProviderLabelKey string 132 } 133 134 // NewService returns a new object responsible for service-layer Subscription operations. 135 func NewService(runtimeSvc RuntimeService, runtimeCtxSvc RuntimeCtxService, tenantSvc TenantService, labelSvc LabelService, appTemplateSvc ApplicationTemplateService, appConv ApplicationConverter, appTemplateConv ApplicationTemplateConverter, appSvc ApplicationService, uidService uidService, 136 consumerSubaccountLabelKey, subscriptionLabelKey, runtimeTypeLabelKey, subscriptionProviderLabelKey string) *service { 137 return &service{ 138 runtimeSvc: runtimeSvc, 139 runtimeCtxSvc: runtimeCtxSvc, 140 tenantSvc: tenantSvc, 141 labelSvc: labelSvc, 142 appTemplateSvc: appTemplateSvc, 143 appConv: appConv, 144 appTemplateConv: appTemplateConv, 145 appSvc: appSvc, 146 uidSvc: uidService, 147 consumerSubaccountLabelKey: consumerSubaccountLabelKey, 148 subscriptionLabelKey: subscriptionLabelKey, 149 runtimeTypeLabelKey: runtimeTypeLabelKey, 150 subscriptionProviderLabelKey: subscriptionProviderLabelKey, 151 } 152 } 153 154 // SubscribeTenantToRuntime subscribes a tenant to runtimes by labeling the runtime 155 func (s *service) SubscribeTenantToRuntime(ctx context.Context, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region, subscriptionAppName string) (bool, error) { 156 log.C(ctx).Infof("Subscribe request is triggerred between consumer with tenant: %q and subaccount: %q and provider with subaccount: %q and application name: %q", consumerTenantID, subaccountTenantID, providerSubaccountID, subscriptionAppName) 157 providerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, providerSubaccountID) 158 if err != nil { 159 return false, errors.Wrapf(err, "while getting provider subaccount internal ID from external ID: %q", providerSubaccountID) 160 } 161 ctx = tenant.SaveToContext(ctx, providerInternalTenant, providerSubaccountID) 162 163 filters := s.buildLabelFilters(providerID, region) 164 log.C(ctx).Infof("Getting provider runtime in tenant %q for labels %q: %q and %q: %q", providerSubaccountID, tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID) 165 runtime, err := s.runtimeSvc.GetByFilters(ctx, filters) 166 if err != nil { 167 if apperrors.IsNotFoundError(err) { 168 return false, nil 169 } 170 171 return false, errors.Wrap(err, fmt.Sprintf("Failed to get runtime for labels %q: %q and %q: %q", tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID)) 172 } 173 174 consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subaccountTenantID) 175 if err != nil { 176 log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during subscription: %v", subaccountTenantID, err) 177 return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subaccountTenantID) 178 } 179 180 runtimeID := runtime.ID 181 log.C(ctx).Infof("Listing runtime context(s) in the consumer tenant %q for label with key: %q and value: %q", subaccountTenantID, s.consumerSubaccountLabelKey, subaccountTenantID) 182 rtmCtxPage, err := s.runtimeCtxSvc.ListByFilter(tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID), runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(s.consumerSubaccountLabelKey, fmt.Sprintf("\"%s\"", subaccountTenantID))}, 100, "") 183 if err != nil { 184 log.C(ctx).Errorf("An error occurred while listing runtime contexts with key: %q and value: %q for runtime with ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID, err) 185 return false, err 186 } 187 log.C(ctx).Infof("Found %d runtime context(s) with key: %q and value: %q for runtime with ID: %q", len(rtmCtxPage.Data), s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID) 188 189 for _, rtmCtx := range rtmCtxPage.Data { 190 if rtmCtx.Value == consumerTenantID { 191 // Already subscribed 192 log.C(ctx).Infof("Consumer %q is already subscribed. Increasing the %q label value by one", consumerTenantID, InstancesLabelKey) 193 if err := s.manageInstancesLabelOnSubscribe(ctx, consumerInternalTenant, model.RuntimeContextLabelableObject, rtmCtx.ID); err != nil { 194 return false, err 195 } 196 return true, nil 197 } 198 } 199 200 tnt, err := s.tenantSvc.GetLowestOwnerForResource(ctx, resource.Runtime, runtime.ID) 201 if err != nil { 202 log.C(ctx).Errorf("An error occurred while getting lowest owner for resource type: %q with ID: %q: %v", resource.Runtime, runtime.ID, err) 203 return false, err 204 } 205 206 log.C(ctx).Debugf("Upserting runtime label with key: %q and value: %q", s.runtimeTypeLabelKey, subscriptionAppName) 207 if err := s.labelSvc.UpsertLabel(ctx, tnt, &model.LabelInput{ 208 Key: s.runtimeTypeLabelKey, 209 Value: subscriptionAppName, 210 ObjectType: model.RuntimeLabelableObject, 211 ObjectID: runtime.ID, 212 }); err != nil { 213 log.C(ctx).Errorf("An error occurred while upserting label with key: %q and value: %q for object type: %q and ID: %q: %v", s.runtimeTypeLabelKey, subscriptionAppName, model.RuntimeLabelableObject, runtime.ID, err) 214 return false, err 215 } 216 217 ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID) 218 219 m2mTable, ok := resource.Runtime.TenantAccessTable() 220 if !ok { 221 return false, errors.Errorf("entity %s does not have access table", resource.Runtime) 222 } 223 224 if err := repo.UpsertTenantAccessRecursively(ctx, m2mTable, &repo.TenantAccess{ 225 TenantID: consumerInternalTenant, 226 ResourceID: runtime.ID, 227 Owner: false, 228 }); err != nil { 229 return false, err 230 } 231 232 rtmCtxID, err := s.runtimeCtxSvc.Create(ctx, model.RuntimeContextInput{ 233 Key: s.subscriptionLabelKey, 234 Value: consumerTenantID, 235 RuntimeID: runtime.ID, 236 }) 237 if err != nil { 238 log.C(ctx).Errorf("An error occurred while creating runtime context with key: %q and value: %q, and runtime ID: %q: %v", s.subscriptionLabelKey, consumerTenantID, runtime.ID, err) 239 return false, errors.Wrapf(err, "while creating runtime context with value: %q and runtime ID: %q during subscription", consumerTenantID, runtime.ID) 240 } 241 242 log.C(ctx).Infof("Creating label for runtime context with ID: %q with key: %q and value: %q", rtmCtxID, s.consumerSubaccountLabelKey, subaccountTenantID) 243 if err := s.labelSvc.CreateLabel(ctx, consumerInternalTenant, s.uidSvc.Generate(), &model.LabelInput{ 244 Key: s.consumerSubaccountLabelKey, 245 Value: subaccountTenantID, 246 ObjectID: rtmCtxID, 247 ObjectType: model.RuntimeContextLabelableObject, 248 }); err != nil { 249 log.C(ctx).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, model.RuntimeContextLabelableObject, rtmCtxID, err) 250 return false, errors.Wrap(err, fmt.Sprintf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", s.consumerSubaccountLabelKey, subaccountTenantID, model.RuntimeContextLabelableObject, rtmCtxID)) 251 } 252 253 log.C(ctx).Infof("Creating label for runtime context with ID: %q with key: %q and value: %q", rtmCtxID, InstancesLabelKey, 1) 254 if err := s.labelSvc.CreateLabel(ctx, consumerInternalTenant, s.uidSvc.Generate(), &model.LabelInput{ 255 Key: InstancesLabelKey, 256 Value: 1, 257 ObjectID: rtmCtxID, 258 ObjectType: model.RuntimeContextLabelableObject, 259 }); err != nil { 260 log.C(ctx).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q: %v", InstancesLabelKey, 1, model.RuntimeContextLabelableObject, rtmCtxID, err) 261 return false, errors.Wrap(err, fmt.Sprintf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, 1, model.RuntimeContextLabelableObject, rtmCtxID)) 262 } 263 264 return true, nil 265 } 266 267 // UnsubscribeTenantFromRuntime unsubscribes a tenant from runtimes by removing labels from runtime 268 func (s *service) UnsubscribeTenantFromRuntime(ctx context.Context, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region string) (bool, error) { 269 log.C(ctx).Infof("Unsubscribe request is triggerred between consumer with tenant: %q and subaccount: %q and provider with subaccount: %q", consumerTenantID, subaccountTenantID, providerSubaccountID) 270 providerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, providerSubaccountID) 271 if err != nil { 272 return false, errors.Wrapf(err, "while getting provider subaccount internal ID from external ID: %q", providerSubaccountID) 273 } 274 ctx = tenant.SaveToContext(ctx, providerInternalTenant, providerSubaccountID) 275 276 filters := s.buildLabelFilters(providerID, region) 277 log.C(ctx).Infof("Getting provider runtime in tenant %q for labels %q: %q and %q: %q", providerSubaccountID, tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID) 278 runtime, err := s.runtimeSvc.GetByFilters(ctx, filters) 279 if err != nil { 280 if apperrors.IsNotFoundError(err) { 281 return false, nil 282 } 283 284 return false, errors.Wrap(err, fmt.Sprintf("Failed to get runtime for labels %q: %q and %q: %q", tenant.RegionLabelKey, region, s.subscriptionProviderLabelKey, providerID)) 285 } 286 287 consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subaccountTenantID) 288 if err != nil { 289 log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during subscription: %v", subaccountTenantID, err) 290 return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subaccountTenantID) 291 } 292 ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subaccountTenantID) 293 294 runtimeID := runtime.ID 295 log.C(ctx).Infof("Listing runtime context(s) in the consumer tenant %q for label with key: %q and value: %q", subaccountTenantID, s.consumerSubaccountLabelKey, subaccountTenantID) 296 rtmCtxPage, err := s.runtimeCtxSvc.ListByFilter(ctx, runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(s.consumerSubaccountLabelKey, fmt.Sprintf("\"%s\"", subaccountTenantID))}, 100, "") 297 if err != nil { 298 log.C(ctx).Errorf("An error occurred while listing runtime contexts with key: %q and value: %q for runtime with ID: %q: %v", s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID, err) 299 return false, err 300 } 301 log.C(ctx).Infof("Found %d runtime context(s) with key: %q and value: %q for runtime with ID: %q", len(rtmCtxPage.Data), s.consumerSubaccountLabelKey, subaccountTenantID, runtimeID) 302 303 for _, rtmCtx := range rtmCtxPage.Data { 304 // if the current subscription(runtime context) is the one for which the unsubscribe request is initiated, delete the record from the DB 305 if rtmCtx.Value == consumerTenantID { 306 if err := s.deleteOnUnsubscribe(ctx, consumerInternalTenant, model.RuntimeContextLabelableObject, rtmCtx.ID, s.runtimeCtxSvc.Delete); err != nil { 307 return false, err 308 } 309 break 310 } 311 } 312 313 return true, nil 314 } 315 316 // SubscribeTenantToApplication fetches model.ApplicationTemplate by region and provider and registers an Application from that template 317 func (s *service) SubscribeTenantToApplication(ctx context.Context, providerID, subscribedSubaccountID, consumerTenantID, region, subscribedAppName string, subscriptionPayload string) (bool, error) { 318 filters := s.buildLabelFilters(providerID, region) 319 appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters) 320 if err != nil { 321 if apperrors.IsNotFoundError(err) { 322 return false, nil 323 } 324 325 return false, errors.Wrapf(err, "while getting application template with filter labels %q and %q", providerID, region) 326 } 327 328 consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subscribedSubaccountID) 329 if err != nil { 330 log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during application subscription: %v", subscribedSubaccountID, err) 331 return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subscribedSubaccountID) 332 } 333 334 ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subscribedSubaccountID) 335 336 applications, err := s.appSvc.ListAll(ctx) 337 if err != nil { 338 return false, errors.Wrapf(err, "while listing applications") 339 } 340 341 for _, app := range applications { 342 if str.PtrStrToStr(app.ApplicationTemplateID) == appTemplate.ID { 343 // Already subscribed 344 log.C(ctx).Infof("Consumer %q is already subscribed. Increasing the %q label value by one", consumerTenantID, InstancesLabelKey) 345 if err := s.manageInstancesLabelOnSubscribe(ctx, consumerInternalTenant, model.ApplicationLabelableObject, app.ID); err != nil { 346 return false, err 347 } 348 return true, nil 349 } 350 } 351 352 subdomainLabel, err := s.labelSvc.GetByKey(ctx, consumerInternalTenant, model.TenantLabelableObject, consumerInternalTenant, SubdomainLabelKey) 353 if err != nil { 354 if !apperrors.IsNotFoundError(err) { 355 return false, errors.Wrapf(err, "while getting label %q for %q with id %q", SubdomainLabelKey, model.TenantLabelableObject, consumerInternalTenant) 356 } 357 } 358 359 subdomainValue := "" 360 if subdomainLabel != nil && subdomainLabel.Value != nil { 361 if subdomainLabelValue, ok := subdomainLabel.Value.(string); ok { 362 subdomainValue = subdomainLabelValue 363 } 364 } 365 366 if err := s.createApplicationFromTemplate(ctx, appTemplate, subscribedSubaccountID, consumerTenantID, subscribedAppName, subdomainValue, region, subscriptionPayload); err != nil { 367 return false, err 368 } 369 370 return true, nil 371 } 372 373 // UnsubscribeTenantFromApplication fetches model.ApplicationTemplate by region and provider, lists all applications for 374 // the subscribedSubaccountID tenant and deletes them synchronously 375 func (s *service) UnsubscribeTenantFromApplication(ctx context.Context, providerID, subscribedSubaccountID, region string) (bool, error) { 376 filters := s.buildLabelFilters(providerID, region) 377 appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters) 378 if err != nil { 379 if apperrors.IsNotFoundError(err) { 380 return false, nil 381 } 382 383 return false, errors.Wrapf(err, "while getting application template with filter labels %q and %q", providerID, region) 384 } 385 386 consumerInternalTenant, err := s.tenantSvc.GetInternalTenant(ctx, subscribedSubaccountID) 387 if err != nil { 388 log.C(ctx).Errorf("An error occurred while getting tenant by external ID: %q during application unsubscription: %v", subscribedSubaccountID, err) 389 return false, errors.Wrapf(err, "while getting tenant with external ID: %q", subscribedSubaccountID) 390 } 391 392 ctx = tenant.SaveToContext(ctx, consumerInternalTenant, subscribedSubaccountID) 393 394 if err := s.deleteApplicationsByAppTemplateID(ctx, appTemplate.ID); err != nil { 395 return false, err 396 } 397 398 return true, nil 399 } 400 401 // DetermineSubscriptionFlow determines if the subscription flow is resource.ApplicationTemplate or resource.Runtime 402 // by fetching both resources by provider and region 403 func (s *service) DetermineSubscriptionFlow(ctx context.Context, providerID, region string) (resource.Type, error) { 404 filters := s.buildLabelFilters(providerID, region) 405 runtime, err := s.runtimeSvc.GetByFiltersGlobal(ctx, filters) 406 if err != nil { 407 if !apperrors.IsNotFoundError(err) { 408 return "", errors.Wrapf(err, "while getting runtime with filter labels provider (%q) and region (%q)", providerID, region) 409 } 410 } 411 412 appTemplate, err := s.appTemplateSvc.GetByFilters(ctx, filters) 413 if err != nil { 414 if !apperrors.IsNotFoundError(err) { 415 return "", errors.Wrapf(err, "while getting app template with filter labels provider (%q) and region (%q)", providerID, region) 416 } 417 } 418 419 if runtime != nil && appTemplate == nil { 420 return resource.Runtime, nil 421 } 422 423 if runtime == nil && appTemplate != nil { 424 return resource.ApplicationTemplate, nil 425 } 426 427 if runtime == nil && appTemplate == nil { 428 return "", nil 429 } 430 431 if runtime != nil && appTemplate != nil { 432 return "", errors.Errorf("both a runtime (%+v) and application template (%+v) exist with filter labels provider (%q) and region (%q)", runtime, appTemplate, providerID, region) 433 } 434 435 return "", errors.Errorf("could not determine flow") 436 } 437 438 func (s *service) createApplicationFromTemplate(ctx context.Context, appTemplate *model.ApplicationTemplate, subscribedSubaccountID, consumerTenantID, subscribedAppName, subdomain, region string, subscriptionPayload string) error { 439 log.C(ctx).Debugf("Preparing Values for Application Template with name %q", appTemplate.Name) 440 values, err := s.preparePlaceholderValues(appTemplate, subdomain, region, subscriptionPayload) 441 if err != nil { 442 return errors.Wrapf(err, "while preparing the values for Application template %q", appTemplate.Name) 443 } 444 445 log.C(ctx).Debugf("Preparing ApplicationCreateInput JSON from Application Template with name %q", appTemplate.Name) 446 appCreateInputJSON, err := s.appTemplateSvc.PrepareApplicationCreateInputJSON(appTemplate, values) 447 if err != nil { 448 return errors.Wrapf(err, "while preparing ApplicationCreateInput JSON from Application Template with name %q", appTemplate.Name) 449 } 450 451 log.C(ctx).Debugf("Converting ApplicationCreateInput JSON to GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name) 452 appCreateInputGQL, err := s.appConv.CreateRegisterInputJSONToGQL(appCreateInputJSON) 453 if err != nil { 454 return errors.Wrapf(err, "while converting ApplicationCreateInput JSON to GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name) 455 } 456 457 log.C(ctx).Infof("Validating GraphQL ApplicationRegistrationInput from Application Template with name %q", appTemplate.Name) 458 if err := inputvalidation.Validate(appCreateInputGQL); err != nil { 459 return errors.Wrapf(err, "while validating application input from Application Template with name %q", appTemplate.Name) 460 } 461 462 appCreateInputModel, err := s.appConv.CreateInputFromGraphQL(ctx, appCreateInputGQL) 463 if err != nil { 464 return errors.Wrap(err, "while converting ApplicationFromTemplate input") 465 } 466 467 if appCreateInputModel.Labels == nil { 468 appCreateInputModel.Labels = make(map[string]interface{}) 469 } 470 appCreateInputModel.Labels["managed"] = "false" 471 appCreateInputModel.Labels[InstancesLabelKey] = 1 472 appCreateInputModel.Labels[s.consumerSubaccountLabelKey] = subscribedSubaccountID 473 appCreateInputModel.LocalTenantID = &consumerTenantID 474 475 log.C(ctx).Infof("Creating an Application with name %q from Application Template with name %q", subscribedAppName, appTemplate.Name) 476 _, err = s.appSvc.CreateFromTemplate(ctx, appCreateInputModel, &appTemplate.ID) 477 if err != nil { 478 return errors.Wrapf(err, "while creating an Application with name %s from Application Template with name %s", subscribedAppName, appTemplate.Name) 479 } 480 481 return nil 482 } 483 484 func (s *service) preparePlaceholderValues(appTemplate *model.ApplicationTemplate, subdomain, region string, subscriptionPayload string) ([]*model.ApplicationTemplateValueInput, error) { 485 values := []*model.ApplicationTemplateValueInput{ 486 {Placeholder: "subdomain", Value: subdomain}, 487 {Placeholder: "region", Value: strings.TrimPrefix(region, RegionPrefix)}, 488 } 489 490 oldPlaceholders := appTemplate.Placeholders 491 492 newPlaceholders := []model.ApplicationTemplatePlaceholder{} 493 for _, placeholder := range oldPlaceholders { 494 if placeholder.Name != "subdomain" && placeholder.Name != "region" { 495 newPlaceholders = append(newPlaceholders, placeholder) 496 } 497 } 498 appTemplate.Placeholders = newPlaceholders 499 500 appFromTemplateInput, err := s.appTemplateConv.ApplicationFromTemplateInputFromGraphQL(appTemplate, graphql.ApplicationFromTemplateInput{ 501 TemplateName: appTemplate.Name, 502 PlaceholdersPayload: &subscriptionPayload, 503 }) 504 505 if err != nil { 506 return nil, errors.Wrapf(err, "while parsing the callback payload with the Application template %q", appTemplate.Name) 507 } 508 509 appTemplate.Placeholders = oldPlaceholders 510 values = append(appFromTemplateInput.Values, values...) 511 return values, nil 512 } 513 514 func (s *service) deleteApplicationsByAppTemplateID(ctx context.Context, appTemplateID string) error { 515 applications, err := s.appSvc.ListAll(ctx) 516 if err != nil { 517 return errors.Wrapf(err, "while listing applications") 518 } 519 520 for _, app := range applications { 521 if str.PtrStrToStr(app.ApplicationTemplateID) == appTemplateID { 522 internalTenant, err := tenant.LoadFromContext(ctx) 523 if err != nil { 524 return errors.Wrapf(err, "An error occurred while loading tenant from context") 525 } 526 if err := s.deleteOnUnsubscribe(ctx, internalTenant, model.ApplicationLabelableObject, app.ID, s.appSvc.Delete); err != nil { 527 return err 528 } 529 } 530 } 531 532 return nil 533 } 534 535 func (s *service) manageInstancesLabelOnSubscribe(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error { 536 instancesLabel, err := s.labelSvc.GetByKey(ctx, tenant, objectType, objectID, InstancesLabelKey) 537 if err != nil { 538 if !apperrors.IsNotFoundError(err) { 539 log.C(ctx).WithError(err).Errorf("An error occurred while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID) 540 return errors.Wrapf(err, "while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID) 541 } 542 543 if err := s.labelSvc.CreateLabel(ctx, tenant, s.uidSvc.Generate(), &model.LabelInput{ 544 Key: InstancesLabelKey, 545 Value: DefaultNumberOfInstancesForAlreadySubscribedTenant, 546 ObjectID: objectID, 547 ObjectType: objectType, 548 }); err != nil { 549 log.C(ctx).WithError(err).Errorf("An error occurred while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, DefaultNumberOfInstancesForAlreadySubscribedTenant, objectType, objectID) 550 return errors.Wrapf(err, "while creating label with key: %q and value: %q for object type: %q and ID: %q", InstancesLabelKey, DefaultNumberOfInstancesForAlreadySubscribedTenant, objectType, objectID) 551 } 552 553 log.C(ctx).Debugf("%q label created, for already subscibed tenant to %q with id %q", InstancesLabelKey, objectType, objectID) 554 return nil 555 } 556 557 instances, ok := instancesLabel.Value.(float64) 558 if !ok { 559 return errors.Errorf("cannot cast %q label value of type %T to int", InstancesLabelKey, instancesLabel.Value) 560 } 561 562 log.C(ctx).Debugf("Increasing %q label value. Current value %f", InstancesLabelKey, instances) 563 instances++ 564 if err := s.labelSvc.UpdateLabel(ctx, tenant, instancesLabel.ID, &model.LabelInput{ 565 Key: instancesLabel.Key, 566 Value: instances, 567 ObjectID: instancesLabel.ObjectID, 568 ObjectType: instancesLabel.ObjectType, 569 Version: instancesLabel.Version, 570 }); err != nil { 571 log.C(ctx).WithError(err).Errorf("An error occurred while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID) 572 return errors.Wrapf(err, "while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID) 573 } 574 575 log.C(ctx).Debugf("Successfully increased %q label value to %f for %q with id %q", InstancesLabelKey, instances, objectType, objectID) 576 return nil 577 } 578 579 func (s *service) deleteOnUnsubscribe(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, deleteObject func(context.Context, string) error) error { 580 instancesLabel, err := s.labelSvc.GetByKey(ctx, tenant, objectType, objectID, InstancesLabelKey) 581 if err != nil { 582 if !apperrors.IsNotFoundError(err) { 583 log.C(ctx).WithError(err).Errorf("An error occurred while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID) 584 return errors.Wrapf(err, "while getting label with key: %q for object type: %q and ID: %q", InstancesLabelKey, objectType, objectID) 585 } 586 587 log.C(ctx).Debugf("Cannot find label with key %q for %q with ID %q. Triggering deletion of %q with ID %q...", InstancesLabelKey, objectType, objectID, objectType, objectID) 588 if err := deleteObject(ctx, objectID); err != nil { 589 log.C(ctx).WithError(err).Errorf("An error occurred while trying to delete %q with ID: %q", objectType, objectID) 590 return errors.Wrapf(err, "while trying to delete %q with ID: %q", objectType, objectID) 591 } 592 log.C(ctx).Infof("Successfully deleted %q with ID %q", objectType, objectID) 593 return nil 594 } 595 596 instances, ok := instancesLabel.Value.(float64) 597 if !ok { 598 return errors.Errorf("cannot cast %q label value of type %T to int", InstancesLabelKey, instancesLabel.Value) 599 } 600 601 if instances <= 1 { 602 log.C(ctx).Debugf("The number of %q for %q with ID %q is <=1. Triggering deletion of %q with ID %q...", InstancesLabelKey, objectType, objectID, objectType, objectID) 603 if err := deleteObject(ctx, objectID); err != nil { 604 log.C(ctx).WithError(err).Errorf("An error occurred while deleting %q with ID: %q", objectType, objectID) 605 return errors.Wrapf(err, "while deleting %q with ID: %q", objectType, objectID) 606 } 607 log.C(ctx).Infof("Successfully deleted %q with ID %q", objectType, objectID) 608 return nil 609 } 610 611 instances-- 612 if err := s.labelSvc.UpdateLabel(ctx, tenant, instancesLabel.ID, &model.LabelInput{ 613 Key: instancesLabel.Key, 614 Value: instances, 615 ObjectID: instancesLabel.ObjectID, 616 ObjectType: instancesLabel.ObjectType, 617 Version: instancesLabel.Version, 618 }); err != nil { 619 log.C(ctx).WithError(err).Errorf("An error occurred while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID) 620 return errors.Wrapf(err, "while updating label with key: %q and value: %f for object type: %q and ID: %q", InstancesLabelKey, instances, objectType, objectID) 621 } 622 log.C(ctx).Debugf("Successfully decreased %q label value to %f for %q with ID %q", InstancesLabelKey, instances, objectType, objectID) 623 624 return nil 625 } 626 627 func (s *service) buildLabelFilters(subscriptionProviderID, region string) []*labelfilter.LabelFilter { 628 return []*labelfilter.LabelFilter{ 629 labelfilter.NewForKeyWithQuery(s.subscriptionProviderLabelKey, fmt.Sprintf("\"%s\"", subscriptionProviderID)), 630 labelfilter.NewForKeyWithQuery(tenant.RegionLabelKey, fmt.Sprintf("\"%s\"", region)), 631 } 632 }