github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/runtime/service.go (about) 1 package runtime 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "strings" 8 "time" 9 10 "github.com/kyma-incubator/compass/components/director/pkg/consumer" 11 12 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 13 14 "github.com/kyma-incubator/compass/components/director/internal/domain/label" 15 "github.com/kyma-incubator/compass/components/director/internal/domain/scenarioassignment" 16 "github.com/kyma-incubator/compass/components/director/pkg/str" 17 18 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 19 "github.com/kyma-incubator/compass/components/director/pkg/log" 20 21 "github.com/kyma-incubator/compass/components/director/internal/labelfilter" 22 "github.com/kyma-incubator/compass/components/director/internal/model" 23 24 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 25 "github.com/pkg/errors" 26 ) 27 28 const ( 29 // IsNormalizedLabel represents the label that is used to mark a runtime as normalized 30 IsNormalizedLabel = "isNormalized" 31 32 // RegionLabelKey is the key of the tenant label for region. 33 RegionLabelKey = "region" 34 ) 35 36 //go:generate mockery --exported --name=runtimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 37 type runtimeRepository interface { 38 Exists(ctx context.Context, tenant, id string) (bool, error) 39 GetByID(ctx context.Context, tenant, id string) (*model.Runtime, error) 40 GetByFiltersGlobal(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.Runtime, error) 41 List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error) 42 ListByFiltersGlobal(context.Context, []*labelfilter.LabelFilter) ([]*model.Runtime, error) 43 Create(ctx context.Context, tenant string, item *model.Runtime) error 44 Update(ctx context.Context, tenant string, item *model.Runtime) error 45 ListAll(context.Context, string, []*labelfilter.LabelFilter) ([]*model.Runtime, error) 46 Delete(ctx context.Context, tenant, id string) error 47 GetByFilters(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) 48 } 49 50 //go:generate mockery --exported --name=labelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 51 type labelRepository interface { 52 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 53 ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) 54 Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error 55 DeleteAll(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error 56 DeleteByKeyNegationPattern(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labelKeyPattern string) error 57 } 58 59 //go:generate mockery --exported --name=labelService --output=automock --outpkg=automock --case=underscore --disable-version-string 60 type labelService interface { 61 UpsertMultipleLabels(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labels map[string]interface{}) error 62 UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error 63 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 64 } 65 66 //go:generate mockery --exported --name=tenantService --output=automock --outpkg=automock --case=underscore --disable-version-string 67 type tenantService interface { 68 GetTenantByExternalID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) 69 GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) 70 } 71 72 //go:generate mockery --exported --name=uidService --output=automock --outpkg=automock --case=underscore --disable-version-string 73 type uidService interface { 74 Generate() string 75 } 76 77 type service struct { 78 repo runtimeRepository 79 labelRepo labelRepository 80 81 labelService labelService 82 uidService uidService 83 formationService formationService 84 tenantSvc tenantService 85 webhookService WebhookService 86 runtimeContextService RuntimeContextService 87 88 protectedLabelPattern string 89 immutableLabelPattern string 90 runtimeTypeLabelKey string 91 kymaRuntimeTypeLabelValue string 92 kymaApplicationNamespaceValue string 93 } 94 95 // NewService missing godoc 96 func NewService(repo runtimeRepository, 97 labelRepo labelRepository, 98 labelService labelService, 99 uidService uidService, 100 formationService formationService, 101 tenantService tenantService, 102 webhookService WebhookService, 103 runtimeContextService RuntimeContextService, 104 protectedLabelPattern, immutableLabelPattern, runtimeTypeLabelKey, kymaRuntimeTypeLabelValue, kymaApplicationNamespaceValue string) *service { 105 return &service{ 106 repo: repo, 107 labelRepo: labelRepo, 108 labelService: labelService, 109 uidService: uidService, 110 formationService: formationService, 111 tenantSvc: tenantService, 112 webhookService: webhookService, 113 runtimeContextService: runtimeContextService, 114 protectedLabelPattern: protectedLabelPattern, 115 immutableLabelPattern: immutableLabelPattern, 116 runtimeTypeLabelKey: runtimeTypeLabelKey, 117 kymaRuntimeTypeLabelValue: kymaRuntimeTypeLabelValue, 118 kymaApplicationNamespaceValue: kymaApplicationNamespaceValue, 119 } 120 } 121 122 // List missing godoc 123 func (s *service) List(ctx context.Context, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error) { 124 rtmTenant, err := tenant.LoadFromContext(ctx) 125 if err != nil { 126 return nil, errors.Wrapf(err, "while loading tenant from context") 127 } 128 129 if pageSize < 1 || pageSize > 200 { 130 return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200") 131 } 132 133 return s.repo.List(ctx, rtmTenant, filter, pageSize, cursor) 134 } 135 136 // Get missing godoc 137 func (s *service) Get(ctx context.Context, id string) (*model.Runtime, error) { 138 rtmTenant, err := tenant.LoadFromContext(ctx) 139 if err != nil { 140 return nil, errors.Wrapf(err, "while loading tenant from context") 141 } 142 143 runtime, err := s.repo.GetByID(ctx, rtmTenant, id) 144 if err != nil { 145 return nil, errors.Wrapf(err, "while getting Runtime with ID %s", id) 146 } 147 148 return runtime, nil 149 } 150 151 // GetByTokenIssuer missing godoc 152 func (s *service) GetByTokenIssuer(ctx context.Context, issuer string) (*model.Runtime, error) { 153 const ( 154 consoleURLLabelKey = "runtime_consoleUrl" 155 dexSubdomain = "dex" 156 consoleSubdomain = "console" 157 ) 158 consoleURL := strings.Replace(issuer, dexSubdomain, consoleSubdomain, 1) 159 160 filters := []*labelfilter.LabelFilter{ 161 labelfilter.NewForKeyWithQuery(consoleURLLabelKey, fmt.Sprintf(`"%s"`, consoleURL)), 162 } 163 164 runtime, err := s.repo.GetByFiltersGlobal(ctx, filters) 165 if err != nil { 166 return nil, errors.Wrapf(err, "while getting the Runtime by the console URL label (%s)", consoleURL) 167 } 168 169 return runtime, nil 170 } 171 172 // GetByFiltersGlobal missing godoc 173 func (s *service) GetByFiltersGlobal(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error) { 174 runtimes, err := s.repo.GetByFiltersGlobal(ctx, filters) 175 if err != nil { 176 return nil, errors.Wrapf(err, "while getting runtimes by filters from repo") 177 } 178 return runtimes, nil 179 } 180 181 // GetByFilters retrieves model.Runtime matching on the given label filters 182 func (s *service) GetByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error) { 183 rtmTenant, err := tenant.LoadFromContext(ctx) 184 if err != nil { 185 return nil, errors.Wrapf(err, "while loading tenant from context") 186 } 187 188 runtime, err := s.repo.GetByFilters(ctx, rtmTenant, filters) 189 if err != nil { 190 return nil, errors.Wrapf(err, "while getting runtime by filters from repo") 191 } 192 return runtime, nil 193 } 194 195 // ListByFiltersGlobal missing godoc 196 func (s *service) ListByFiltersGlobal(ctx context.Context, filters []*labelfilter.LabelFilter) ([]*model.Runtime, error) { 197 runtimes, err := s.repo.ListByFiltersGlobal(ctx, filters) 198 if err != nil { 199 return nil, errors.Wrapf(err, "while getting runtimes by filters from repo") 200 } 201 return runtimes, nil 202 } 203 204 // ListByFilters lists all runtimes in a given tenant that match given label filter. 205 func (s *service) ListByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) ([]*model.Runtime, error) { 206 rtmTenant, err := tenant.LoadFromContext(ctx) 207 if err != nil { 208 return nil, errors.Wrapf(err, "while loading tenant from context") 209 } 210 211 runtimes, err := s.repo.ListAll(ctx, rtmTenant, filters) 212 if err != nil { 213 return nil, errors.Wrapf(err, "while getting runtimes by filters from repo") 214 } 215 return runtimes, nil 216 } 217 218 // Exist missing godoc 219 func (s *service) Exist(ctx context.Context, id string) (bool, error) { 220 rtmTenant, err := tenant.LoadFromContext(ctx) 221 if err != nil { 222 return false, errors.Wrapf(err, "while loading tenant from context") 223 } 224 225 exist, err := s.repo.Exists(ctx, rtmTenant, id) 226 if err != nil { 227 return false, errors.Wrapf(err, "while getting Runtime with ID %s", id) 228 } 229 230 return exist, nil 231 } 232 233 // Create creates a runtime in a given tenant. 234 // If the runtime has a global_subaccount_id label which value is a valid external subaccount from our DB and a child of the caller tenant. The subaccount is used to register the runtime. 235 // After successful registration, the ASAs in the parent of the caller tenant are processed to add all matching scenarios for the runtime in the parent tenant. 236 func (s *service) Create(ctx context.Context, in model.RuntimeRegisterInput) (string, error) { 237 labels := make(map[string]interface{}) 238 id := s.uidService.Generate() 239 return id, s.CreateWithMandatoryLabels(ctx, in, id, labels) 240 } 241 242 // CreateWithMandatoryLabels creates a runtime in a given tenant and also adds mandatory labels to it. 243 func (s *service) CreateWithMandatoryLabels(ctx context.Context, in model.RuntimeRegisterInput, id string, mandatoryLabels map[string]interface{}) error { 244 var subaccountTnt string 245 if saVal, ok := in.Labels[scenarioassignment.SubaccountIDKey]; ok { // TODO: <backwards-compatibility>: Should be deleted once the provisioner start creating runtimes in a subaccount 246 tnt, err := s.extractTenantFromSubaccountLabel(ctx, saVal) 247 if err != nil { 248 return err 249 } 250 subaccountTnt = tnt.ID 251 ctx = tenant.SaveToContext(ctx, tnt.ID, tnt.ExternalTenant) 252 } 253 254 rtmTenant, err := tenant.LoadFromContext(ctx) 255 if err != nil { 256 return errors.Wrapf(err, "while loading tenant from context") 257 } 258 259 consumerInfo, err := consumer.LoadFromContext(ctx) 260 if err != nil { 261 return errors.Wrapf(err, "while loading consumer") 262 } 263 264 isConsumerIntegrationSystem := consumerInfo.ConsumerType == consumer.IntegrationSystem 265 if isConsumerIntegrationSystem { 266 in.ApplicationNamespace = &s.kymaApplicationNamespaceValue 267 } 268 269 rtm := in.ToRuntime(id, time.Now(), time.Now()) 270 271 if err = s.repo.Create(ctx, rtmTenant, rtm); err != nil { 272 return errors.Wrapf(err, "while creating Runtime") 273 } 274 275 if in.Labels == nil || in.Labels[IsNormalizedLabel] == nil { 276 if in.Labels == nil { 277 in.Labels = make(map[string]interface{}, 1) 278 } 279 in.Labels[IsNormalizedLabel] = "true" 280 } 281 282 log.C(ctx).Debugf("Removing protected labels. Labels before: %+v", in.Labels) 283 if in.Labels, err = s.UnsafeExtractModifiableLabels(in.Labels); err != nil { 284 return err 285 } 286 log.C(ctx).Debugf("Successfully stripped protected labels. Resulting labels after operation are: %+v", in.Labels) 287 288 for key, value := range mandatoryLabels { 289 in.Labels[key] = value 290 } 291 292 var scenariosToAssign interface{} = nil 293 if _, areScenariosInLabels := in.Labels[model.ScenariosKey]; areScenariosInLabels { 294 scenariosToAssign = in.Labels[model.ScenariosKey] 295 delete(in.Labels, model.ScenariosKey) 296 } 297 298 if isConsumerIntegrationSystem { 299 in.Labels[s.runtimeTypeLabelKey] = s.kymaRuntimeTypeLabelValue 300 301 region, err := s.extractRegionFromSubaccountTenant(ctx, subaccountTnt) 302 if err != nil { 303 return err 304 } 305 in.Labels[RegionLabelKey] = region 306 } 307 308 if err = s.labelService.UpsertMultipleLabels(ctx, rtmTenant, model.RuntimeLabelableObject, id, in.Labels); err != nil { 309 return errors.Wrapf(err, "while creating multiple labels for Runtime") 310 } 311 312 if scenariosToAssign != nil { 313 if err := s.assignRuntimeScenarios(ctx, rtmTenant, id, scenariosToAssign); err != nil { 314 return err 315 } 316 } 317 318 for _, w := range in.Webhooks { 319 if _, err = s.webhookService.Create(ctx, rtm.ID, *w, model.RuntimeWebhookReference); err != nil { 320 return errors.Wrap(err, "while Creating Webhook for Runtime") 321 } 322 } 323 324 // The runtime is created successfully, however there can be ASAs in the parent that should be processed. 325 tnt, err := s.tenantSvc.GetTenantByID(ctx, rtmTenant) 326 if err != nil { 327 return errors.Wrapf(err, "while getting tenant with id %s", rtmTenant) 328 } 329 330 if len(tnt.Parent) == 0 { 331 return nil 332 } 333 334 ctxWithParentTenant := tenant.SaveToContext(ctx, tnt.Parent, "") 335 336 mergedScenarios, err := s.formationService.MergeScenariosFromInputLabelsAndAssignments(ctxWithParentTenant, map[string]interface{}{}, id) 337 if err != nil { 338 return errors.Wrap(err, "while merging scenarios from input and assignments") 339 } 340 341 if err := s.assignRuntimeScenarios(ctxWithParentTenant, tnt.Parent, id, mergedScenarios); err != nil { 342 return errors.Wrapf(err, "while assigning merged formations") 343 } 344 345 return nil 346 } 347 348 // Update updates Runtime and its labels 349 func (s *service) Update(ctx context.Context, id string, in model.RuntimeUpdateInput) error { 350 rtmTenant, err := tenant.LoadFromContext(ctx) 351 if err != nil { 352 return errors.Wrapf(err, "while loading tenant from context") 353 } 354 355 rtm, err := s.repo.GetByID(ctx, rtmTenant, id) 356 if err != nil { 357 return errors.Wrapf(err, "while getting Runtime with id %s", id) 358 } 359 360 rtm.SetFromUpdateInput(in, id, rtm.CreationTimestamp, time.Now()) 361 362 if err = s.repo.Update(ctx, rtmTenant, rtm); err != nil { 363 return errors.Wrap(err, "while updating Runtime") 364 } 365 366 if in.Labels == nil || in.Labels[IsNormalizedLabel] == nil { 367 if in.Labels == nil { 368 in.Labels = make(map[string]interface{}, 1) 369 } 370 in.Labels[IsNormalizedLabel] = "true" 371 } 372 373 if err := s.updateScenariosLabel(ctx, rtmTenant, id, in.Labels); err != nil { 374 return errors.Wrap(err, "while updating scenarios label") 375 } 376 delete(in.Labels, model.ScenariosKey) 377 378 log.C(ctx).Debugf("Removing protected labels. Labels before: %+v", in.Labels) 379 if in.Labels, err = s.UnsafeExtractModifiableLabels(in.Labels); err != nil { 380 return err 381 } 382 log.C(ctx).Debugf("Successfully stripped protected labels. Resulting labels after operation are: %+v", in.Labels) 383 384 unmodifiablePattern := s.protectedLabelPattern + "|^" + model.ScenariosKey + "$" + "|" + s.immutableLabelPattern 385 // NOTE: The db layer does not support OR currently so multiple label patterns can't be implemented easily 386 if err = s.labelRepo.DeleteByKeyNegationPattern(ctx, rtmTenant, model.RuntimeLabelableObject, id, unmodifiablePattern); err != nil { 387 return errors.Wrapf(err, "while deleting all labels for Runtime") 388 } 389 390 if err = s.labelService.UpsertMultipleLabels(ctx, rtmTenant, model.RuntimeLabelableObject, id, in.Labels); err != nil { 391 return errors.Wrapf(err, "while creating multiple labels for Runtime") 392 } 393 394 return nil 395 } 396 397 // Delete deletes all RuntimeContexts associated with the runtime with ID `id` and then deletes the runtime and its labels 398 func (s *service) Delete(ctx context.Context, id string) error { 399 rtmTenant, err := tenant.LoadFromContext(ctx) 400 if err != nil { 401 return errors.Wrapf(err, "while loading tenant from context") 402 } 403 404 runtimeContexts, err := s.runtimeContextService.ListAllForRuntime(ctx, id) 405 if err != nil { 406 return errors.Wrapf(err, "while listing runtimeContexts for runtime with ID %q", id) 407 } 408 409 for _, rc := range runtimeContexts { 410 if err = s.runtimeContextService.Delete(ctx, rc.ID); err != nil { 411 return errors.Wrapf(err, "while deleting runtimeContext with ID %q", rc.ID) 412 } 413 } 414 415 if err = s.unassignRuntimeScenarios(ctx, rtmTenant, id); err != nil { 416 return err 417 } 418 419 if err = s.repo.Delete(ctx, rtmTenant, id); err != nil { 420 return errors.Wrapf(err, "while deleting Runtime") 421 } 422 423 // All labels are deleted (cascade delete) 424 425 return nil 426 } 427 428 // SetLabel sets Runtime label from a given input 429 func (s *service) SetLabel(ctx context.Context, labelInput *model.LabelInput) error { 430 rtmTenant, err := tenant.LoadFromContext(ctx) 431 if err != nil { 432 return errors.Wrapf(err, "while loading tenant from context") 433 } 434 435 if err = s.ensureRuntimeExists(ctx, rtmTenant, labelInput.ObjectID); err != nil { 436 return err 437 } 438 439 if modifiable, err := isLabelModifiable(labelInput.Key, s.protectedLabelPattern, s.immutableLabelPattern); err != nil { 440 return err 441 } else if !modifiable { 442 return apperrors.NewInvalidDataError("could not set unmodifiable label with key %s", labelInput.Key) 443 } 444 445 id := labelInput.ObjectID 446 447 if labelInput.Key == model.ScenariosKey { 448 if err := s.updateScenariosLabel(ctx, rtmTenant, id, map[string]interface{}{model.ScenariosKey: labelInput.Value}); err != nil { 449 return errors.Wrap(err, "while updating scenarios label") 450 } 451 } else { 452 if err = s.labelService.UpsertLabel(ctx, rtmTenant, labelInput); err != nil { 453 return errors.Wrapf(err, "while creating label for Runtime") 454 } 455 } 456 457 return nil 458 } 459 460 // GetLabel missing godoc 461 func (s *service) GetLabel(ctx context.Context, runtimeID string, key string) (*model.Label, error) { 462 rtmTenant, err := tenant.LoadFromContext(ctx) 463 if err != nil { 464 return nil, errors.Wrapf(err, "while loading tenant from context") 465 } 466 467 rtmExists, err := s.repo.Exists(ctx, rtmTenant, runtimeID) 468 if err != nil { 469 return nil, errors.Wrap(err, "while checking Runtime existence") 470 } 471 if !rtmExists { 472 return nil, fmt.Errorf("Runtime with ID %s doesn't exist", runtimeID) 473 } 474 475 label, err := s.labelRepo.GetByKey(ctx, rtmTenant, model.RuntimeLabelableObject, runtimeID, key) 476 if err != nil { 477 return nil, errors.Wrap(err, "while getting label for Runtime") 478 } 479 480 return label, nil 481 } 482 483 // ListLabels missing godoc 484 func (s *service) ListLabels(ctx context.Context, runtimeID string) (map[string]*model.Label, error) { 485 rtmTenant, err := tenant.LoadFromContext(ctx) 486 if err != nil { 487 return nil, errors.Wrapf(err, "while loading tenant from context") 488 } 489 490 rtmExists, err := s.repo.Exists(ctx, rtmTenant, runtimeID) 491 if err != nil { 492 return nil, errors.Wrap(err, "while checking Runtime existence") 493 } 494 495 if !rtmExists { 496 return nil, fmt.Errorf("Runtime with ID %s doesn't exist", runtimeID) 497 } 498 499 labels, err := s.labelRepo.ListForObject(ctx, rtmTenant, model.RuntimeLabelableObject, runtimeID) 500 if err != nil { 501 return nil, errors.Wrap(err, "while getting label for Runtime") 502 } 503 504 return extractUnProtectedLabels(labels, s.protectedLabelPattern) 505 } 506 507 // DeleteLabel deletes Runtime label from a given label key 508 func (s *service) DeleteLabel(ctx context.Context, runtimeID string, key string) error { 509 rtmTenant, err := tenant.LoadFromContext(ctx) 510 if err != nil { 511 return errors.Wrapf(err, "while loading tenant from context") 512 } 513 514 if err = s.ensureRuntimeExists(ctx, rtmTenant, runtimeID); err != nil { 515 return err 516 } 517 518 if modifiable, err := isLabelModifiable(key, s.protectedLabelPattern, s.immutableLabelPattern); err != nil { 519 return err 520 } else if !modifiable { 521 return apperrors.NewInvalidDataError("could not delete unmodifiable label with key %s", key) 522 } 523 524 if key == model.ScenariosKey { 525 if err := s.unassignRuntimeScenarios(ctx, rtmTenant, runtimeID); err != nil { 526 return err 527 } 528 } else { 529 if err = s.labelRepo.Delete(ctx, rtmTenant, model.RuntimeLabelableObject, runtimeID, key); err != nil { 530 return errors.Wrapf(err, "while deleting Runtime label") 531 } 532 } 533 534 return nil 535 } 536 537 // UnsafeExtractModifiableLabels returns all labels except the protected and immutable labels 538 func (s *service) UnsafeExtractModifiableLabels(labels map[string]interface{}) (map[string]interface{}, error) { 539 result := make(map[string]interface{}) 540 for labelKey, lbl := range labels { 541 modifiable, err := isLabelModifiable(labelKey, s.protectedLabelPattern, s.immutableLabelPattern) 542 if err != nil { 543 return nil, err 544 } 545 if modifiable { 546 result[labelKey] = lbl 547 } 548 } 549 return result, nil 550 } 551 552 func (s *service) ensureRuntimeExists(ctx context.Context, tnt string, runtimeID string) error { 553 rtmExists, err := s.repo.Exists(ctx, tnt, runtimeID) 554 if err != nil { 555 return errors.Wrap(err, "while checking Runtime existence") 556 } 557 if !rtmExists { 558 return fmt.Errorf("Runtime with ID %s doesn't exist", runtimeID) 559 } 560 561 return nil 562 } 563 564 func (s *service) assignRuntimeScenarios(ctx context.Context, rtmTenant, id string, scenarios interface{}) error { 565 scenariosStr, err := label.ValueToStringsSlice(scenarios) 566 if err != nil { 567 return errors.Wrapf(err, "while converting scenarios: %+v to slice of strings", scenarios) 568 } 569 570 for _, scenario := range scenariosStr { 571 if _, err = s.formationService.AssignFormation(ctx, rtmTenant, id, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}); err != nil { 572 return errors.Wrapf(err, "while assigning formation %q from runtime with ID %q", scenario, id) 573 } 574 } 575 576 return nil 577 } 578 579 func (s *service) unassignRuntimeScenarios(ctx context.Context, rtmTenant, runtimeID string) error { 580 currentRuntimeLabels, err := s.getCurrentLabelsForRuntime(ctx, rtmTenant, runtimeID) 581 if err != nil { 582 return err 583 } 584 585 if scenarios, areScenariosInLabels := currentRuntimeLabels[model.ScenariosKey]; areScenariosInLabels { 586 scenariosStr, err := label.ValueToStringsSlice(scenarios) 587 if err != nil { 588 return errors.Wrapf(err, "while converting scenarios: %+v to slice of strings", scenarios) 589 } 590 591 for _, scenario := range scenariosStr { 592 if _, err = s.formationService.UnassignFormation(ctx, rtmTenant, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}); err != nil { 593 return errors.Wrapf(err, "while unassigning formation %q from runtime with ID %q", scenario, runtimeID) 594 } 595 } 596 } 597 598 return nil 599 } 600 601 func (s *service) updateScenariosLabel(ctx context.Context, rtmTenant, rtmID string, inputLabels map[string]interface{}) error { 602 mergedScenarios, err := s.formationService.MergeScenariosFromInputLabelsAndAssignments(ctx, inputLabels, rtmID) 603 if err != nil { 604 return errors.Wrap(err, "while merging scenarios from input and assignments") 605 } 606 607 mergedScenariosSlice, err := label.ValueToStringsSlice(mergedScenarios) 608 if err != nil { 609 return errors.Wrapf(err, "while converting merged scenarios: %+v to slice of strings", mergedScenarios) 610 } 611 612 currentRuntimeLabels, err := s.getCurrentLabelsForRuntime(ctx, rtmTenant, rtmID) 613 if err != nil { 614 return err 615 } 616 617 currentScenarios, areCurrentScenariosInLabels := currentRuntimeLabels[model.ScenariosKey] 618 if !areCurrentScenariosInLabels { 619 currentScenarios = []interface{}{} 620 } 621 622 currentScenariosSlice, err := label.ValueToStringsSlice(currentScenarios) 623 if err != nil { 624 return errors.Wrapf(err, "while converting current runtime scenarios: %+v to slice of strings", currentScenarios) 625 } 626 627 currentScenariosMap := make(map[string]struct{}, len(currentScenariosSlice)) 628 for _, s := range currentScenariosSlice { 629 currentScenariosMap[s] = struct{}{} 630 } 631 632 mergedScenariosMap := make(map[string]struct{}, len(mergedScenariosSlice)) 633 for _, s := range mergedScenariosSlice { 634 mergedScenariosMap[s] = struct{}{} 635 } 636 637 for scenario := range currentScenariosMap { 638 if _, found := mergedScenariosMap[scenario]; !found { 639 if _, err = s.formationService.UnassignFormation(ctx, rtmTenant, rtmID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}); err != nil { 640 return errors.Wrapf(err, "while unassigning formation %q from runtime with ID %q", scenario, rtmID) 641 } 642 } 643 } 644 645 for scenario := range mergedScenariosMap { 646 if _, found := currentScenariosMap[scenario]; !found { 647 if _, err = s.formationService.AssignFormation(ctx, rtmTenant, rtmID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}); err != nil { 648 return errors.Wrapf(err, "while assigning formation %q from runtime with ID %q", scenario, rtmID) 649 } 650 } 651 } 652 653 return nil 654 } 655 656 func (s *service) getCurrentLabelsForRuntime(ctx context.Context, tenantID, runtimeID string) (map[string]interface{}, error) { 657 labels, err := s.labelRepo.ListForObject(ctx, tenantID, model.RuntimeLabelableObject, runtimeID) 658 if err != nil { 659 return nil, err 660 } 661 662 currentLabels := make(map[string]interface{}) 663 for _, v := range labels { 664 currentLabels[v.Key] = v.Value 665 } 666 return currentLabels, nil 667 } 668 669 func (s *service) extractTenantFromSubaccountLabel(ctx context.Context, value interface{}) (*model.BusinessTenantMapping, error) { 670 callingTenant, err := tenant.LoadFromContext(ctx) 671 if err != nil { 672 return nil, errors.Wrapf(err, "while loading tenant from context") 673 } 674 675 sa, err := convertLabelValue(value) 676 if err != nil { 677 return nil, errors.Wrapf(err, "while converting %s label", scenarioassignment.SubaccountIDKey) 678 } 679 680 log.C(ctx).Infof("Runtime registered by tenant %s with %s label with value %s. Will proceed with the subaccount as tenant...", callingTenant, scenarioassignment.SubaccountIDKey, sa) 681 682 tnt, err := s.tenantSvc.GetTenantByExternalID(ctx, sa) 683 if err != nil { 684 return nil, errors.Wrapf(err, "while getting tenant %s", sa) 685 } 686 687 if callingTenant != tnt.ID && callingTenant != tnt.Parent { 688 log.C(ctx).Errorf("Caller tenant %s is not parent of the subaccount %s in the %s label", callingTenant, sa, scenarioassignment.SubaccountIDKey) 689 return nil, apperrors.NewInvalidOperationError(fmt.Sprintf("Tenant provided in %s label should be child of the caller tenant", scenarioassignment.SubaccountIDKey)) 690 } 691 return tnt, nil 692 } 693 694 func (s *service) extractRegionFromSubaccountTenant(ctx context.Context, subaccountTnt string) (string, error) { 695 if subaccountTnt == "" { 696 return "", nil 697 } 698 699 regionLabel, err := s.labelService.GetByKey(ctx, subaccountTnt, model.TenantLabelableObject, subaccountTnt, RegionLabelKey) 700 if err != nil { 701 if !apperrors.IsNotFoundError(err) { 702 return "", errors.Wrapf(err, "while getting label %q for %q with id %q", RegionLabelKey, model.TenantLabelableObject, subaccountTnt) 703 } 704 } 705 706 regionValue := "" 707 if regionLabel != nil && regionLabel.Value != nil { 708 if regionLabelValue, ok := regionLabel.Value.(string); ok { 709 regionValue = regionLabelValue 710 } 711 } 712 713 return regionValue, nil 714 } 715 716 func extractUnProtectedLabels(labels map[string]*model.Label, protectedLabelsKeyPattern string) (map[string]*model.Label, error) { 717 result := make(map[string]*model.Label) 718 for labelKey, label := range labels { 719 protected, err := regexp.MatchString(protectedLabelsKeyPattern, labelKey) 720 if err != nil { 721 return nil, err 722 } 723 if !protected { 724 result[labelKey] = label 725 } 726 } 727 return result, nil 728 } 729 730 func isLabelModifiable(labelKey, protectedLabelsKeyPattern, immutableLabelsKeyPattern string) (bool, error) { 731 protected, err := regexp.MatchString(protectedLabelsKeyPattern, labelKey) 732 if err != nil { 733 return false, err 734 } 735 immutable, err := regexp.MatchString(immutableLabelsKeyPattern, labelKey) 736 if err != nil { 737 return false, err 738 } 739 return !protected && !immutable, err 740 } 741 742 func convertLabelValue(value interface{}) (string, error) { 743 values, err := label.ValueToStringsSlice(value) 744 if err != nil { 745 result := str.CastOrEmpty(value) 746 if len(result) == 0 { 747 return "", errors.New("cannot cast label value: expected []string or string") 748 } 749 return result, nil 750 } 751 if len(values) != 1 { 752 return "", errors.New("expected single value for label") 753 } 754 return values[0], nil 755 }