github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/application/service.go (about) 1 package application 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/url" 8 "strings" 9 10 "github.com/kyma-incubator/compass/components/director/pkg/accessstrategy" 11 12 "github.com/imdario/mergo" 13 14 "github.com/kyma-incubator/compass/components/director/internal/domain/eventing" 15 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 16 "github.com/kyma-incubator/compass/components/director/pkg/operation" 17 "github.com/kyma-incubator/compass/components/director/pkg/str" 18 19 "github.com/kyma-incubator/compass/components/director/pkg/log" 20 21 "github.com/kyma-incubator/compass/components/director/pkg/normalizer" 22 23 "github.com/kyma-incubator/compass/components/director/pkg/resource" 24 25 "github.com/kyma-incubator/compass/components/director/internal/domain/label" 26 27 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 28 29 "github.com/google/uuid" 30 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 31 "github.com/kyma-incubator/compass/components/director/internal/labelfilter" 32 "github.com/kyma-incubator/compass/components/director/internal/model" 33 "github.com/kyma-incubator/compass/components/director/internal/timestamp" 34 "github.com/kyma-incubator/compass/components/director/pkg/pagination" 35 "github.com/pkg/errors" 36 ) 37 38 const ( 39 intSysKey = "integrationSystemID" 40 nameKey = "name" 41 sccLabelKey = "scc" 42 managedKey = "managed" 43 subaccountKey = "Subaccount" 44 locationIDKey = "LocationID" 45 urlSuffixToBeTrimmed = "/" 46 applicationTypeLabelKey = "applicationType" 47 ppmsProductVersionIDLabelKey = "ppmsProductVersionId" 48 urlSubdomainSeparator = "." 49 ) 50 51 type repoCreatorFunc func(ctx context.Context, tenant string, application *model.Application) error 52 type repoUpserterFunc func(ctx context.Context, tenant string, application *model.Application) (string, error) 53 54 // ApplicationRepository missing godoc 55 // 56 //go:generate mockery --name=ApplicationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 57 type ApplicationRepository interface { 58 Exists(ctx context.Context, tenant, id string) (bool, error) 59 OwnerExists(ctx context.Context, tenant, id string) (bool, error) 60 GetByID(ctx context.Context, tenant, id string) (*model.Application, error) 61 GetByIDForUpdate(ctx context.Context, tenant, id string) (*model.Application, error) 62 GetGlobalByID(ctx context.Context, id string) (*model.Application, error) 63 GetBySystemNumber(ctx context.Context, tenant, systemNumber string) (*model.Application, error) 64 GetByFilter(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Application, error) 65 List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.ApplicationPage, error) 66 ListAll(ctx context.Context, tenant string) ([]*model.Application, error) 67 ListAllByFilter(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Application, error) 68 ListGlobal(ctx context.Context, pageSize int, cursor string) (*model.ApplicationPage, error) 69 ListAllByApplicationTemplateID(ctx context.Context, applicationTemplateID string) ([]*model.Application, error) 70 ListByScenarios(ctx context.Context, tenantID uuid.UUID, scenarios []string, pageSize int, cursor string, hidingSelectors map[string][]string) (*model.ApplicationPage, error) 71 ListByScenariosNoPaging(ctx context.Context, tenant string, scenarios []string) ([]*model.Application, error) 72 ListListeningApplications(ctx context.Context, tenant string, whType model.WebhookType) ([]*model.Application, error) 73 ListAllByIDs(ctx context.Context, tenantID string, ids []string) ([]*model.Application, error) 74 ListByScenariosAndIDs(ctx context.Context, tenant string, scenarios []string, ids []string) ([]*model.Application, error) 75 Create(ctx context.Context, tenant string, item *model.Application) error 76 Update(ctx context.Context, tenant string, item *model.Application) error 77 Upsert(ctx context.Context, tenant string, model *model.Application) (string, error) 78 TrustedUpsert(ctx context.Context, tenant string, model *model.Application) (string, error) 79 TechnicalUpdate(ctx context.Context, item *model.Application) error 80 Delete(ctx context.Context, tenant, id string) error 81 DeleteGlobal(ctx context.Context, id string) error 82 } 83 84 // LabelRepository missing godoc 85 // 86 //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 87 type LabelRepository interface { 88 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 89 ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) 90 ListGlobalByKey(ctx context.Context, key string) ([]*model.Label, error) 91 ListGlobalByKeyAndObjects(ctx context.Context, objectType model.LabelableObject, objectIDs []string, key string) ([]*model.Label, error) 92 Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error 93 DeleteAll(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error 94 } 95 96 // WebhookRepository missing godoc 97 // 98 //go:generate mockery --name=WebhookRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 99 type WebhookRepository interface { 100 CreateMany(ctx context.Context, tenant string, items []*model.Webhook) error 101 ListByReferenceObjectID(ctx context.Context, tenant, objID string, objType model.WebhookReferenceObjectType) ([]*model.Webhook, error) 102 } 103 104 // FormationService missing godoc 105 // 106 //go:generate mockery --name=FormationService --output=automock --outpkg=automock --case=underscore --disable-version-string 107 type FormationService interface { 108 AssignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) 109 UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) 110 } 111 112 // RuntimeRepository missing godoc 113 // 114 //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 115 type RuntimeRepository interface { 116 Exists(ctx context.Context, tenant, id string) (bool, error) 117 ListAll(ctx context.Context, tenantID string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) 118 } 119 120 // IntegrationSystemRepository missing godoc 121 // 122 //go:generate mockery --name=IntegrationSystemRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 123 type IntegrationSystemRepository interface { 124 Exists(ctx context.Context, id string) (bool, error) 125 } 126 127 // LabelService missing godoc 128 // 129 //go:generate mockery --name=LabelService --output=automock --outpkg=automock --case=underscore --disable-version-string 130 type LabelService interface { 131 UpsertMultipleLabels(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labels map[string]interface{}) error 132 UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error 133 GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) 134 } 135 136 // UIDService missing godoc 137 // 138 //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string 139 type UIDService interface { 140 Generate() string 141 } 142 143 // ApplicationHideCfgProvider missing godoc 144 // 145 //go:generate mockery --name=ApplicationHideCfgProvider --output=automock --outpkg=automock --case=underscore --disable-version-string 146 type ApplicationHideCfgProvider interface { 147 GetApplicationHideSelectors() (map[string][]string, error) 148 } 149 150 type service struct { 151 appNameNormalizer normalizer.Normalizator 152 appHideCfgProvider ApplicationHideCfgProvider 153 154 appRepo ApplicationRepository 155 webhookRepo WebhookRepository 156 labelRepo LabelRepository 157 runtimeRepo RuntimeRepository 158 intSystemRepo IntegrationSystemRepository 159 160 labelService LabelService 161 uidService UIDService 162 bndlService BundleService 163 timestampGen timestamp.Generator 164 formationService FormationService 165 166 selfRegisterDistinguishLabelKey string 167 168 ordWebhookMapping []ORDWebhookMapping 169 } 170 171 // NewService missing godoc 172 func NewService(appNameNormalizer normalizer.Normalizator, appHideCfgProvider ApplicationHideCfgProvider, app ApplicationRepository, webhook WebhookRepository, runtimeRepo RuntimeRepository, labelRepo LabelRepository, intSystemRepo IntegrationSystemRepository, labelService LabelService, bndlService BundleService, uidService UIDService, formationService FormationService, selfRegisterDistinguishLabelKey string, ordWebhookMapping []ORDWebhookMapping) *service { 173 return &service{ 174 appNameNormalizer: appNameNormalizer, 175 appHideCfgProvider: appHideCfgProvider, 176 appRepo: app, 177 webhookRepo: webhook, 178 runtimeRepo: runtimeRepo, 179 labelRepo: labelRepo, 180 intSystemRepo: intSystemRepo, 181 labelService: labelService, 182 bndlService: bndlService, 183 uidService: uidService, 184 timestampGen: timestamp.DefaultGenerator, 185 formationService: formationService, 186 selfRegisterDistinguishLabelKey: selfRegisterDistinguishLabelKey, 187 ordWebhookMapping: ordWebhookMapping, 188 } 189 } 190 191 // List missing godoc 192 func (s *service) List(ctx context.Context, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.ApplicationPage, error) { 193 appTenant, err := tenant.LoadFromContext(ctx) 194 if err != nil { 195 return nil, errors.Wrapf(err, "while loading tenant from context") 196 } 197 198 if pageSize < 1 || pageSize > 200 { 199 return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200") 200 } 201 202 return s.appRepo.List(ctx, appTenant, filter, pageSize, cursor) 203 } 204 205 // ListAll lists tenant scoped applications 206 func (s *service) ListAll(ctx context.Context) ([]*model.Application, error) { 207 appTenant, err := tenant.LoadFromContext(ctx) 208 if err != nil { 209 return nil, errors.Wrapf(err, "while loading tenant from context") 210 } 211 212 return s.appRepo.ListAll(ctx, appTenant) 213 } 214 215 // ListGlobal missing godoc 216 func (s *service) ListGlobal(ctx context.Context, pageSize int, cursor string) (*model.ApplicationPage, error) { 217 if pageSize < 1 || pageSize > 200 { 218 return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200") 219 } 220 221 return s.appRepo.ListGlobal(ctx, pageSize, cursor) 222 } 223 224 // ListAllByApplicationTemplateID lists all applications which have the given app template id 225 func (s *service) ListAllByApplicationTemplateID(ctx context.Context, applicationTemplateID string) ([]*model.Application, error) { 226 apps, err := s.appRepo.ListAllByApplicationTemplateID(ctx, applicationTemplateID) 227 if err != nil { 228 return nil, errors.Wrapf(err, "while getting applications for app template with id %q", applicationTemplateID) 229 } 230 231 if len(apps) == 0 { 232 return []*model.Application{}, nil 233 } 234 235 return apps, nil 236 } 237 238 // ListByRuntimeID missing godoc 239 func (s *service) ListByRuntimeID(ctx context.Context, runtimeID uuid.UUID, pageSize int, cursor string) (*model.ApplicationPage, error) { 240 tenantID, err := tenant.LoadFromContext(ctx) 241 242 if err != nil { 243 return nil, errors.Wrapf(err, "while loading tenant from context") 244 } 245 246 tenantUUID, err := uuid.Parse(tenantID) 247 if err != nil { 248 return nil, apperrors.NewInvalidDataError("tenantID is not UUID") 249 } 250 251 exist, err := s.runtimeRepo.Exists(ctx, tenantID, runtimeID.String()) 252 if err != nil { 253 return nil, errors.Wrap(err, "while checking if runtime exits") 254 } 255 256 if !exist { 257 return nil, apperrors.NewInvalidDataError("runtime does not exist") 258 } 259 260 scenariosLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), model.ScenariosKey) 261 if err != nil { 262 if apperrors.IsNotFoundError(err) { 263 return &model.ApplicationPage{ 264 Data: []*model.Application{}, 265 PageInfo: &pagination.Page{}, 266 TotalCount: 0, 267 }, nil 268 } 269 return nil, errors.Wrap(err, "while getting scenarios for runtime") 270 } 271 272 scenarios, err := label.ValueToStringsSlice(scenariosLabel.Value) 273 if err != nil { 274 return nil, errors.Wrap(err, "while converting scenarios labels") 275 } 276 if len(scenarios) == 0 { 277 return &model.ApplicationPage{ 278 Data: []*model.Application{}, 279 TotalCount: 0, 280 PageInfo: &pagination.Page{ 281 StartCursor: "", 282 EndCursor: "", 283 HasNextPage: false, 284 }, 285 }, nil 286 } 287 288 hidingSelectors, err := s.appHideCfgProvider.GetApplicationHideSelectors() 289 if err != nil { 290 return nil, errors.Wrap(err, "while getting application hide selectors from config") 291 } 292 293 return s.appRepo.ListByScenarios(ctx, tenantUUID, scenarios, pageSize, cursor, hidingSelectors) 294 } 295 296 // Get missing godoc 297 func (s *service) Get(ctx context.Context, id string) (*model.Application, error) { 298 appTenant, err := tenant.LoadFromContext(ctx) 299 if err != nil { 300 return nil, errors.Wrapf(err, "while loading tenant from context") 301 } 302 303 app, err := s.appRepo.GetByID(ctx, appTenant, id) 304 if err != nil { 305 return nil, errors.Wrapf(err, "while getting Application with id %s", id) 306 } 307 308 return app, nil 309 } 310 311 // GetForUpdate returns an application retrieved globally (without tenant required in the context) 312 func (s *service) GetForUpdate(ctx context.Context, id string) (*model.Application, error) { 313 appTenant, err := tenant.LoadFromContext(ctx) 314 if err != nil { 315 return nil, errors.Wrapf(err, "while loading tenant from context") 316 } 317 app, err := s.appRepo.GetByIDForUpdate(ctx, appTenant, id) 318 if err != nil { 319 return nil, errors.Wrapf(err, "while getting Application with id %s", id) 320 } 321 322 return app, nil 323 } 324 325 // GetBySystemNumber returns an application retrieved by systemNumber 326 func (s *service) GetBySystemNumber(ctx context.Context, systemNumber string) (*model.Application, error) { 327 appTenant, err := tenant.LoadFromContext(ctx) 328 if err != nil { 329 return nil, errors.Wrapf(err, "while loading tenant from context") 330 } 331 332 app, err := s.appRepo.GetBySystemNumber(ctx, appTenant, systemNumber) 333 if err != nil { 334 return nil, errors.Wrapf(err, "while getting Application with system number %s", systemNumber) 335 } 336 337 return app, nil 338 } 339 340 // Exist missing godoc 341 func (s *service) Exist(ctx context.Context, id string) (bool, error) { 342 appTenant, err := tenant.LoadFromContext(ctx) 343 if err != nil { 344 return false, errors.Wrapf(err, "while loading tenant from context") 345 } 346 347 exist, err := s.appRepo.Exists(ctx, appTenant, id) 348 if err != nil { 349 return false, errors.Wrapf(err, "while getting Application with ID %s", id) 350 } 351 352 return exist, nil 353 } 354 355 // Create missing godoc 356 func (s *service) Create(ctx context.Context, in model.ApplicationRegisterInput) (string, error) { 357 creator := func(ctx context.Context, tenant string, application *model.Application) (err error) { 358 if err = s.appRepo.Create(ctx, tenant, application); err != nil { 359 return errors.Wrapf(err, "while creating Application with name %s", application.Name) 360 } 361 return 362 } 363 364 return s.genericCreate(ctx, in, creator) 365 } 366 367 // GetSccSystem retrieves an application with label key "scc" and value that matches specified subaccount, location id and virtual host 368 func (s *service) GetSccSystem(ctx context.Context, sccSubaccount, locationID, virtualHost string) (*model.Application, error) { 369 appTenant, err := tenant.LoadFromContext(ctx) 370 if err != nil { 371 return nil, errors.Wrapf(err, "while loading tenant from context") 372 } 373 374 sccLabel := struct { 375 Host string `json:"Host"` 376 Subaccount string `json:"Subaccount"` 377 LocationID string `json:"LocationID"` 378 }{ 379 virtualHost, sccSubaccount, locationID, 380 } 381 marshal, err := json.Marshal(sccLabel) 382 if err != nil { 383 return nil, errors.Wrapf(err, "while marshaling sccLabel with subaccount: %s, locationId: %s and virtualHost: %s", appTenant, locationID, virtualHost) 384 } 385 386 filter := labelfilter.NewForKeyWithQuery(sccLabelKey, string(marshal)) 387 388 app, err := s.appRepo.GetByFilter(ctx, appTenant, []*labelfilter.LabelFilter{filter}) 389 if err != nil { 390 return nil, errors.Wrapf(err, "while getting Application with subaccount: %s, locationId: %s and virtualHost: %s", appTenant, locationID, virtualHost) 391 } 392 393 return app, nil 394 } 395 396 // ListBySCC retrieves all applications with label matching the specified filter 397 func (s *service) ListBySCC(ctx context.Context, filter *labelfilter.LabelFilter) ([]*model.ApplicationWithLabel, error) { 398 appTenant, err := tenant.LoadFromContext(ctx) 399 if err != nil { 400 return nil, errors.Wrapf(err, "while loading tenant from context") 401 } 402 403 apps, err := s.appRepo.ListAllByFilter(ctx, appTenant, []*labelfilter.LabelFilter{filter}) 404 if err != nil { 405 return nil, errors.Wrapf(err, "while getting Applications by filters: %v", filter) 406 } 407 408 if len(apps) == 0 { 409 return []*model.ApplicationWithLabel{}, nil 410 } 411 412 appIDs := make([]string, 0, len(apps)) 413 for _, app := range apps { 414 appIDs = append(appIDs, app.ID) 415 } 416 417 labels, err := s.labelRepo.ListGlobalByKeyAndObjects(ctx, model.ApplicationLabelableObject, appIDs, sccLabelKey) 418 if err != nil { 419 return nil, errors.Wrapf(err, "while getting labels with key scc for applications with IDs: %v", appIDs) 420 } 421 422 appIDToLabel := make(map[string]*model.Label, len(labels)) 423 for _, l := range labels { 424 appIDToLabel[l.ObjectID] = l 425 } 426 427 appsWithLabel := make([]*model.ApplicationWithLabel, 0, len(apps)) 428 for _, app := range apps { 429 appWithLabel := &model.ApplicationWithLabel{ 430 App: app, 431 SccLabel: appIDToLabel[app.ID], 432 } 433 appsWithLabel = append(appsWithLabel, appWithLabel) 434 } 435 436 return appsWithLabel, nil 437 } 438 439 // ListSCCs retrieves all SCCs 440 func (s *service) ListSCCs(ctx context.Context) ([]*model.SccMetadata, error) { 441 labels, err := s.labelRepo.ListGlobalByKey(ctx, sccLabelKey) 442 if err != nil { 443 return nil, errors.Wrap(err, "while getting SCCs by label key: scc") 444 } 445 sccs := make([]*model.SccMetadata, 0, len(labels)) 446 for _, sccLabel := range labels { 447 v, ok := sccLabel.Value.(map[string]interface{}) 448 if !ok { 449 return nil, errors.New("Label value is not of type map[string]interface{}") 450 } 451 452 scc := &model.SccMetadata{ 453 Subaccount: v[subaccountKey].(string), 454 LocationID: v[locationIDKey].(string), 455 } 456 457 sccs = append(sccs, scc) 458 } 459 return sccs, nil 460 } 461 462 // CreateFromTemplate missing godoc 463 func (s *service) CreateFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) (string, error) { 464 creator := func(ctx context.Context, tenant string, application *model.Application) (err error) { 465 application.ApplicationTemplateID = appTemplateID 466 if err = s.appRepo.Create(ctx, tenant, application); err != nil { 467 return errors.Wrapf(err, "while creating Application with name %s from template", application.Name) 468 } 469 return 470 } 471 472 return s.genericCreate(ctx, in, creator) 473 } 474 475 // CreateManyIfNotExistsWithEventualTemplate missing godoc 476 func (s *service) CreateManyIfNotExistsWithEventualTemplate(ctx context.Context, applicationInputs []model.ApplicationRegisterInputWithTemplate) error { 477 appsToAdd, err := s.filterUniqueNonExistingApplications(ctx, applicationInputs) 478 if err != nil { 479 return errors.Wrap(err, "while filtering unique and non-existing applications") 480 } 481 log.C(ctx).Infof("Will create %d systems", len(appsToAdd)) 482 for _, a := range appsToAdd { 483 if a.TemplateID == "" { 484 _, err = s.Create(ctx, a.ApplicationRegisterInput) 485 if err != nil { 486 return errors.Wrap(err, "while creating application") 487 } 488 continue 489 } 490 _, err = s.CreateFromTemplate(ctx, a.ApplicationRegisterInput, &a.TemplateID) 491 if err != nil { 492 return errors.Wrap(err, "while creating application") 493 } 494 } 495 496 return nil 497 } 498 499 // Update missing godoc 500 func (s *service) Update(ctx context.Context, id string, in model.ApplicationUpdateInput) error { 501 appTenant, err := tenant.LoadFromContext(ctx) 502 if err != nil { 503 return errors.Wrapf(err, "while loading tenant from context") 504 } 505 506 exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID) 507 if err != nil { 508 return errors.Wrap(err, "while validating Integration System ID") 509 } 510 511 if !exists { 512 return apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID) 513 } 514 515 app, err := s.Get(ctx, id) 516 if err != nil { 517 return errors.Wrapf(err, "while getting Application with id %s", id) 518 } 519 520 app.SetFromUpdateInput(in, s.timestampGen()) 521 522 if err = s.appRepo.Update(ctx, appTenant, app); err != nil { 523 return errors.Wrapf(err, "while updating Application with id %s", id) 524 } 525 526 if in.IntegrationSystemID != nil { 527 intSysLabel := createLabel(intSysKey, *in.IntegrationSystemID, id) 528 err = s.SetLabel(ctx, intSysLabel) 529 if err != nil { 530 return errors.Wrapf(err, "while setting the integration system label for %s with id %s", intSysLabel.ObjectType, intSysLabel.ObjectID) 531 } 532 log.C(ctx).Debugf("Successfully set Label for %s with id %s", intSysLabel.ObjectType, intSysLabel.ObjectID) 533 } 534 535 label := createLabel(nameKey, s.appNameNormalizer.Normalize(app.Name), app.ID) 536 err = s.SetLabel(ctx, label) 537 if err != nil { 538 return errors.Wrap(err, "while setting application name label") 539 } 540 log.C(ctx).Debugf("Successfully set Label for Application with id %s", app.ID) 541 542 appTypeLbl, err := s.labelService.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, app.ID, applicationTypeLabelKey) 543 if err != nil { 544 if !apperrors.IsNotFoundError(err) { 545 return errors.Wrapf(err, "while getting label %q for %s with id %q", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID) 546 } 547 548 log.C(ctx).Infof("Label %q is missing for %s with id %q. Skipping ord webhook creation", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID) 549 return nil 550 } 551 552 ppmsProductVersionIDLbl, err := s.labelService.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, app.ID, ppmsProductVersionIDLabelKey) 553 if err != nil { 554 if !apperrors.IsNotFoundError(err) { 555 return errors.Wrapf(err, "while getting label %q for %q with id %q", ppmsProductVersionIDLabelKey, model.ApplicationLabelableObject, app.ID) 556 } 557 } 558 559 ppmsProductVersionID := "" 560 if ppmsProductVersionIDLbl != nil && ppmsProductVersionIDLbl.Value != nil { 561 if ppmsProductVersionIDValue, ok := ppmsProductVersionIDLbl.Value.(string); ok { 562 ppmsProductVersionID = ppmsProductVersionIDValue 563 } 564 } 565 566 ordWebhook := s.prepareORDWebhook(ctx, str.PtrStrToStr(in.BaseURL), appTypeLbl.Value.(string), ppmsProductVersionID) 567 if ordWebhook == nil { 568 log.C(ctx).Infof("Skipping ORD Webhook creation for app with id %q.", app.ID) 569 return nil 570 } 571 572 if err = s.createWebhooksIfNotExist(ctx, app.ID, appTenant, []*model.WebhookInput{ordWebhook}); err != nil { 573 return errors.Wrapf(err, "while processing webhooks for application with id %q", app.ID) 574 } 575 576 return nil 577 } 578 579 // Upsert persists application or update it if it already exists 580 func (s *service) Upsert(ctx context.Context, in model.ApplicationRegisterInput) error { 581 tenant, err := tenant.LoadFromContext(ctx) 582 if err != nil { 583 return errors.Wrapf(err, "while loading tenant from context") 584 } 585 586 upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) { 587 id, err := s.appRepo.Upsert(ctx, tenant, application) 588 if err != nil { 589 return "", errors.Wrapf(err, "while upserting Application with name %s", application.Name) 590 } 591 return id, nil 592 } 593 594 return s.genericUpsert(ctx, tenant, in, upserterFunc) 595 } 596 597 // UpdateBaseURL Gets application by ID. If the application does not have a BaseURL set, the API TargetURL is parsed and set as BaseURL 598 func (s *service) UpdateBaseURL(ctx context.Context, appID, targetURL string) error { 599 appTenant, err := tenant.LoadFromContext(ctx) 600 if err != nil { 601 return errors.Wrapf(err, "while loading tenant from context") 602 } 603 604 app, err := s.Get(ctx, appID) 605 if err != nil { 606 return err 607 } 608 609 if app.BaseURL != nil && len(*app.BaseURL) > 0 { 610 log.C(ctx).Infof("BaseURL for Application %s already exists. Will not update it.", appID) 611 return nil 612 } 613 614 log.C(ctx).Infof("BaseURL for Application %s does not exist. Will update it.", appID) 615 616 parsedTargetURL, err := url.Parse(targetURL) 617 if err != nil { 618 return errors.Wrapf(err, "while parsing targetURL") 619 } 620 621 app.BaseURL = str.Ptr(fmt.Sprintf("%s://%s", parsedTargetURL.Scheme, parsedTargetURL.Host)) 622 623 return s.appRepo.Update(ctx, appTenant, app) 624 } 625 626 // TrustedUpsert persists application or update it if it already exists ignoring tenant isolation 627 func (s *service) TrustedUpsert(ctx context.Context, in model.ApplicationRegisterInput) error { 628 tenant, err := tenant.LoadFromContext(ctx) 629 if err != nil { 630 return errors.Wrapf(err, "while loading tenant from context") 631 } 632 633 upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) { 634 id, err := s.appRepo.TrustedUpsert(ctx, tenant, application) 635 if err != nil { 636 return "", errors.Wrapf(err, "while upserting Application with name %s", application.Name) 637 } 638 return id, nil 639 } 640 641 return s.genericUpsert(ctx, tenant, in, upserterFunc) 642 } 643 644 // TrustedUpsertFromTemplate persists application from template id or update it if it already exists ignoring tenant isolation 645 func (s *service) TrustedUpsertFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) error { 646 tenant, err := tenant.LoadFromContext(ctx) 647 if err != nil { 648 return errors.Wrapf(err, "while loading tenant from context") 649 } 650 651 upserterFunc := func(ctx context.Context, tenant string, application *model.Application) (string, error) { 652 application.ApplicationTemplateID = appTemplateID 653 id, err := s.appRepo.TrustedUpsert(ctx, tenant, application) 654 if err != nil { 655 return "", errors.Wrapf(err, "while upserting Application with name %s from template", application.Name) 656 } 657 return id, nil 658 } 659 660 return s.genericUpsert(ctx, tenant, in, upserterFunc) 661 } 662 663 // Delete missing godoc 664 func (s *service) Delete(ctx context.Context, id string) error { 665 appTenant, err := tenant.LoadFromContext(ctx) 666 if err != nil { 667 return errors.Wrapf(err, "while loading tenant from context") 668 } 669 670 if err := s.ensureApplicationNotPartOfAnyScenario(ctx, appTenant, id); err != nil { 671 return err 672 } 673 674 err = s.appRepo.Delete(ctx, appTenant, id) 675 if err != nil { 676 return errors.Wrapf(err, "while deleting Application with id %s", id) 677 } 678 679 return nil 680 } 681 682 // Unpair Checks if the given application is in a scenario with a runtime. Fails if it is. 683 // When the operation mode is sync, it sets the status condition to model.ApplicationStatusConditionInitial and does a db update, otherwise it only makes an "empty" db update. 684 func (s *service) Unpair(ctx context.Context, id string) error { 685 appTenant, err := tenant.LoadFromContext(ctx) 686 if err != nil { 687 return errors.Wrapf(err, "while loading tenant from context") 688 } 689 690 if err := s.ensureApplicationNotPartOfScenarioWithRuntime(ctx, appTenant, id); err != nil { 691 return err 692 } 693 694 app, err := s.appRepo.GetByID(ctx, appTenant, id) 695 if err != nil { 696 return err 697 } 698 699 if opMode := operation.ModeFromCtx(ctx); opMode == graphql.OperationModeSync { 700 app.Status = &model.ApplicationStatus{ 701 Condition: model.ApplicationStatusConditionInitial, 702 Timestamp: s.timestampGen(), 703 } 704 } 705 706 if err = s.appRepo.Update(ctx, appTenant, app); err != nil { 707 return err 708 } 709 710 return nil 711 } 712 713 // SetLabel updates application label with given input label 714 // In the case of a scenario label, it assigns the newly added formations from the input and 715 // unassigns old formations that are not present in the input label, but are stored in the database 716 func (s *service) SetLabel(ctx context.Context, labelInput *model.LabelInput) error { 717 appTenant, err := tenant.LoadFromContext(ctx) 718 if err != nil { 719 return errors.Wrapf(err, "while loading tenant from context") 720 } 721 722 appExists, err := s.appRepo.Exists(ctx, appTenant, labelInput.ObjectID) 723 if err != nil { 724 return errors.Wrap(err, "while checking Application existence") 725 } 726 if !appExists { 727 return apperrors.NewNotFoundError(resource.Application, labelInput.ObjectID) 728 } 729 730 if labelInput.Key == model.ScenariosKey { 731 return s.setScenarioLabel(ctx, appTenant, labelInput) 732 } 733 734 err = s.labelService.UpsertLabel(ctx, appTenant, labelInput) 735 if err != nil { 736 return errors.Wrapf(err, "while creating label for Application") 737 } 738 739 return nil 740 } 741 742 // GetLabel missing godoc 743 func (s *service) GetLabel(ctx context.Context, applicationID string, key string) (*model.Label, error) { 744 appTenant, err := tenant.LoadFromContext(ctx) 745 if err != nil { 746 return nil, errors.Wrapf(err, "while loading tenant from context") 747 } 748 749 appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID) 750 if err != nil { 751 return nil, errors.Wrap(err, "while checking Application existence") 752 } 753 if !appExists { 754 return nil, fmt.Errorf("application with ID %s doesn't exist", applicationID) 755 } 756 757 label, err := s.labelRepo.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key) 758 if err != nil { 759 return nil, errors.Wrap(err, "while getting label for Application") 760 } 761 762 return label, nil 763 } 764 765 // ListLabels missing godoc 766 func (s *service) ListLabels(ctx context.Context, applicationID string) (map[string]*model.Label, error) { 767 appTenant, err := tenant.LoadFromContext(ctx) 768 if err != nil { 769 return nil, errors.Wrapf(err, "while loading tenant from context") 770 } 771 772 appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID) 773 if err != nil { 774 return nil, errors.Wrap(err, "while checking Application existence") 775 } 776 777 if !appExists { 778 return nil, errors.Errorf("application with ID %s doesn't exist", applicationID) 779 } 780 781 labels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, applicationID) 782 if err != nil { 783 return nil, errors.Wrap(err, "while getting label for Application") 784 } 785 786 return labels, nil 787 } 788 789 // DeleteLabel delete label given application ID, label key and label value. 790 func (s *service) DeleteLabel(ctx context.Context, applicationID string, key string) error { 791 appTenant, err := tenant.LoadFromContext(ctx) 792 if err != nil { 793 return errors.Wrapf(err, "while loading tenant from context") 794 } 795 796 appExists, err := s.appRepo.Exists(ctx, appTenant, applicationID) 797 if err != nil { 798 return errors.Wrap(err, "while checking Application existence") 799 } 800 if !appExists { 801 return errors.Errorf("application with ID %s doesn't exist", applicationID) 802 } 803 804 if key == model.ScenariosKey { 805 storedLabel, err := s.labelRepo.GetByKey(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key) 806 if err != nil { 807 return errors.Wrapf(err, "while getting scenario label for %s", applicationID) 808 } 809 scenarios, err := label.ValueToStringsSlice(storedLabel.Value) 810 if err != nil { 811 return errors.Wrapf(err, "while converting label to string slice") 812 } 813 if err = s.unassignFormations(ctx, appTenant, applicationID, scenarios, allowAllCriteria); err != nil { 814 return errors.Wrapf(err, "while unassigning formations") 815 } 816 return nil 817 } 818 819 err = s.labelRepo.Delete(ctx, appTenant, model.ApplicationLabelableObject, applicationID, key) 820 if err != nil { 821 return errors.Wrapf(err, "while deleting Application label") 822 } 823 824 return nil 825 } 826 827 // Merge merges properties from Source Application into Destination Application, provided that the Destination's 828 // Application does not have a value set for a given property. Then the Source Application is being deleted. 829 func (s *service) Merge(ctx context.Context, destID, srcID string) (*model.Application, error) { 830 appTenant, err := tenant.LoadFromContext(ctx) 831 if err != nil { 832 return nil, errors.Wrapf(err, "while loading tenant from context") 833 } 834 835 srcApp, err := s.Get(ctx, srcID) 836 if err != nil { 837 return nil, errors.Wrapf(err, "while getting source application") 838 } 839 840 if err := s.ensureApplicationNotPartOfAnyScenario(ctx, appTenant, srcID); err != nil { 841 return nil, err 842 } 843 844 destApp, err := s.Get(ctx, destID) 845 if err != nil { 846 return nil, errors.Wrapf(err, "while getting destination application") 847 } 848 849 destAppLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, destID) 850 if err != nil { 851 return nil, errors.Wrapf(err, "while getting labels for Application with id %s", destID) 852 } 853 854 srcAppLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.ApplicationLabelableObject, srcID) 855 if err != nil { 856 return nil, errors.Wrapf(err, "while getting labels for Application with id %s", srcID) 857 } 858 859 if destAppLabels == nil { 860 destAppLabels = make(map[string]*model.Label) 861 } 862 863 if srcAppLabels == nil { 864 srcAppLabels = make(map[string]*model.Label) 865 } 866 867 srcBaseURL := strings.TrimSuffix(str.PtrStrToStr(srcApp.BaseURL), urlSuffixToBeTrimmed) 868 destBaseURL := strings.TrimSuffix(str.PtrStrToStr(destApp.BaseURL), urlSuffixToBeTrimmed) 869 if len(srcBaseURL) == 0 || len(destBaseURL) == 0 || srcBaseURL != destBaseURL { 870 return nil, errors.Errorf("BaseURL for applications %s and %s are not the same. Destination app BaseURL: %s. Source app BaseURL: %s", destID, srcID, destBaseURL, srcBaseURL) 871 } 872 873 srcTemplateID := str.PtrStrToStr(srcApp.ApplicationTemplateID) 874 destTemplateID := str.PtrStrToStr(destApp.ApplicationTemplateID) 875 if len(srcTemplateID) == 0 || len(destTemplateID) == 0 || srcTemplateID != destTemplateID { 876 return nil, errors.Errorf("Application templates are not the same. Destination app template: %s. Source app template: %s", destTemplateID, srcTemplateID) 877 } 878 879 appTemplateLabels, err := s.labelRepo.ListForObject(ctx, appTenant, model.AppTemplateLabelableObject, srcTemplateID) 880 if err != nil { 881 return nil, errors.Wrapf(err, "while getting labels for app template with id %s", srcTemplateID) 882 } 883 884 if _, exists := appTemplateLabels[s.selfRegisterDistinguishLabelKey]; exists { 885 log.C(ctx).Infof("applications should not be merged, because an application template with id %s has label %s", srcTemplateID, s.selfRegisterDistinguishLabelKey) 886 return nil, errors.Errorf("app template: %s has label %s", srcTemplateID, s.selfRegisterDistinguishLabelKey) 887 } 888 if srcApp.Status == nil { 889 return nil, errors.Errorf("Could not determine status of source application with id %s", srcID) 890 } 891 892 if srcApp.Status.Condition != model.ApplicationStatusConditionInitial { 893 return nil, errors.Errorf("Cannot merge application with id %s, because it is in a %s status", srcID, model.ApplicationStatusConditionConnected) 894 } 895 896 log.C(ctx).Infof("Merging applications with ids %s and %s", destID, srcID) 897 if err := mergo.Merge(destApp, *srcApp); err != nil { 898 return nil, errors.Wrapf(err, "while trying to merge applications with ids %s and %s", destID, srcID) 899 } 900 901 log.C(ctx).Infof("Merging labels for applications with ids %s and %s", destID, srcID) 902 destAppLabelsMerged, err := s.handleMergeLabels(ctx, srcAppLabels, destAppLabels) 903 if err != nil { 904 return nil, errors.Wrapf(err, "while trying to merge labels for applications with ids %s and %s", destID, srcID) 905 } 906 907 log.C(ctx).Infof("Deleting source application with id %s", srcID) 908 if err := s.Delete(ctx, srcID); err != nil { 909 return nil, err 910 } 911 912 log.C(ctx).Infof("Updating destination app with id %s", srcID) 913 if err := s.appRepo.Update(ctx, appTenant, destApp); err != nil { 914 return nil, err 915 } 916 917 if err := s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, destID, destAppLabelsMerged); err != nil { 918 return nil, err 919 } 920 921 return s.appRepo.GetByID(ctx, appTenant, destID) 922 } 923 924 // handleMergeLabels merges source labels into destination labels. managedKey label is merged manually. 925 // It is updated only if the source or destination label have a value "true" 926 func (s *service) handleMergeLabels(ctx context.Context, srcAppLabels, destAppLabels map[string]*model.Label) (map[string]interface{}, error) { 927 destScenarios, ok := destAppLabels[model.ScenariosKey] 928 if !ok { 929 log.C(ctx).Infof("No %q label found in destination object.", model.ScenariosKey) 930 destScenarios = &model.Label{Value: make([]interface{}, 0)} 931 } 932 933 destScenariosStrSlice, err := label.ValueToStringsSlice(destScenarios.Value) 934 if err != nil { 935 return nil, errors.Wrapf(err, "while converting destination application labels to string slice") 936 } 937 938 if err := mergo.Merge(&destAppLabels, srcAppLabels); err != nil { 939 return nil, errors.Wrapf(err, "while trying to merge labels") 940 } 941 942 destAppLabels[model.ScenariosKey].Value = destScenariosStrSlice 943 944 srcLabelManaged, ok := srcAppLabels[managedKey] 945 if !ok { 946 log.C(ctx).Infof("No %q label found in source object.", managedKey) 947 srcLabelManaged = &model.Label{Value: "false"} 948 } 949 950 srcLabelManagedValue, err := str.CastToBool(srcLabelManaged.Value) 951 if err != nil { 952 return nil, errors.Wrapf(err, "while converting %s value for source label with ID: %s", managedKey, srcAppLabels[managedKey].ID) 953 } 954 955 destLabelManaged, ok := destAppLabels[managedKey] 956 if !ok { 957 log.C(ctx).Infof("No %q label found in destination object.", managedKey) 958 destLabelManaged = &model.Label{Value: "false"} 959 } 960 961 destLabelManagedValue, err := str.CastToBool(destLabelManaged.Value) 962 if err != nil { 963 return nil, errors.Wrapf(err, "while converting %s value for destination label with ID: %s", managedKey, destAppLabels[managedKey].ID) 964 } 965 966 if destLabelManagedValue || srcLabelManagedValue { 967 destAppLabels[managedKey].Value = "true" 968 } 969 970 conv := make(map[string]interface{}, len(destAppLabels)) 971 for key, val := range destAppLabels { 972 conv[key] = val.Value 973 } 974 975 return conv, nil 976 } 977 978 // ensureApplicationNotPartOfScenarioWithRuntime Checks if an application has scenarios associated with it. if a runtime is part of any scenario, then the application is considered being used by that runtime. 979 func (s *service) ensureApplicationNotPartOfScenarioWithRuntime(ctx context.Context, tenant, appID string) error { 980 scenarios, err := s.getScenarioNamesForApplication(ctx, appID) 981 if err != nil { 982 return err 983 } 984 985 if len(scenarios) > 0 { 986 runtimes, err := s.getRuntimeNamesForScenarios(ctx, tenant, scenarios) 987 if err != nil { 988 return err 989 } 990 991 if len(runtimes) > 0 { 992 application, err := s.appRepo.GetByID(ctx, tenant, appID) 993 if err != nil { 994 return errors.Wrapf(err, "while getting application with id %s", appID) 995 } 996 msg := fmt.Sprintf("System %s is still used and cannot be deleted. Unassign the system from the following formations first: %s. Then, unassign the system from the following runtimes, too: %s", application.Name, strings.Join(scenarios, ", "), strings.Join(runtimes, ", ")) 997 return apperrors.NewInvalidOperationError(msg) 998 } 999 1000 return nil 1001 } 1002 1003 return nil 1004 } 1005 1006 // ensureApplicationNotPartOfAnyScenario Checks if an application has scenarios associated with it. If the application is 1007 // associated with any scenario it can not be deleted before unassigning from that scenario 1008 func (s *service) ensureApplicationNotPartOfAnyScenario(ctx context.Context, tenant, appID string) error { 1009 scenarios, err := s.getScenarioNamesForApplication(ctx, appID) 1010 if err != nil { 1011 return err 1012 } 1013 1014 if len(scenarios) > 0 { 1015 application, err := s.appRepo.GetByID(ctx, tenant, appID) 1016 if err != nil { 1017 return errors.Wrapf(err, "while getting application with id %s", appID) 1018 } 1019 msg := fmt.Sprintf("System %s is part of the following formations : %s", application.Name, strings.Join(scenarios, ", ")) 1020 return apperrors.NewInvalidOperationError(msg) 1021 } 1022 1023 return nil 1024 } 1025 1026 func (s *service) createRelatedResources(ctx context.Context, in model.ApplicationRegisterInput, tenant string, applicationID string) error { 1027 var err error 1028 webhooks := make([]*model.Webhook, 0, len(in.Webhooks)) 1029 for _, item := range in.Webhooks { 1030 webhooks = append(webhooks, item.ToWebhook(s.uidService.Generate(), applicationID, model.ApplicationWebhookReference)) 1031 } 1032 if err = s.webhookRepo.CreateMany(ctx, tenant, webhooks); err != nil { 1033 return errors.Wrapf(err, "while creating Webhooks for application") 1034 } 1035 1036 return nil 1037 } 1038 1039 func (s *service) genericCreate(ctx context.Context, in model.ApplicationRegisterInput, repoCreatorFunc repoCreatorFunc) (string, error) { 1040 appTenant, err := tenant.LoadFromContext(ctx) 1041 if err != nil { 1042 return "", err 1043 } 1044 log.C(ctx).Debugf("Loaded Application Tenant %s from context", appTenant) 1045 1046 applications, err := s.appRepo.ListAll(ctx, appTenant) 1047 if err != nil { 1048 return "", err 1049 } 1050 1051 normalizedName := s.appNameNormalizer.Normalize(in.Name) 1052 for _, app := range applications { 1053 if normalizedName == s.appNameNormalizer.Normalize(app.Name) && in.SystemNumber == app.SystemNumber { 1054 return "", apperrors.NewNotUniqueNameError(resource.Application) 1055 } 1056 } 1057 1058 exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID) 1059 if err != nil { 1060 return "", errors.Wrap(err, "while ensuring integration system exists") 1061 } 1062 1063 if !exists { 1064 return "", apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID) 1065 } 1066 1067 id := s.uidService.Generate() 1068 log.C(ctx).Debugf("ID %s generated for Application with name %s", id, in.Name) 1069 1070 app := in.ToApplication(s.timestampGen(), id) 1071 1072 if err = repoCreatorFunc(ctx, appTenant, app); err != nil { 1073 return "", err 1074 } 1075 1076 if in.Labels == nil { 1077 in.Labels = map[string]interface{}{} 1078 } 1079 in.Labels[intSysKey] = "" 1080 if in.IntegrationSystemID != nil { 1081 in.Labels[intSysKey] = *in.IntegrationSystemID 1082 } 1083 in.Labels[nameKey] = normalizedName 1084 1085 var scenariosToAssign []string 1086 if scenarioLabel, ok := in.Labels[model.ScenariosKey]; ok { 1087 scenariosToAssign, err = label.ValueToStringsSlice(scenarioLabel) 1088 if err != nil { 1089 return "", errors.Wrapf(err, "while parsing formations from scenario label") 1090 } 1091 1092 // In order for the scenario label not to be attempted to be created during upsert later 1093 delete(in.Labels, model.ScenariosKey) 1094 } 1095 1096 err = s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, id, in.Labels) 1097 if err != nil { 1098 return id, errors.Wrapf(err, "while creating multiple labels for Application with id %s", id) 1099 } 1100 1101 if err = s.assignFormations(ctx, appTenant, id, scenariosToAssign, allowAllCriteria); err != nil { 1102 return "", errors.Wrapf(err, "while assigning formations") 1103 } 1104 1105 err = s.createRelatedResources(ctx, in, appTenant, app.ID) 1106 if err != nil { 1107 return "", errors.Wrapf(err, "while creating related resources for Application with id %s", id) 1108 } 1109 1110 if in.Bundles != nil { 1111 if err = s.bndlService.CreateMultiple(ctx, resource.Application, id, in.Bundles); err != nil { 1112 return "", errors.Wrapf(err, "while creating related Bundle resources for Application with id %s", id) 1113 } 1114 } 1115 1116 return id, nil 1117 } 1118 1119 func (s *service) filterUniqueNonExistingApplications(ctx context.Context, applicationInputs []model.ApplicationRegisterInputWithTemplate) ([]model.ApplicationRegisterInputWithTemplate, error) { 1120 appTenant, err := tenant.LoadFromContext(ctx) 1121 if err != nil { 1122 return nil, errors.Wrap(err, "while loading tenant from context") 1123 } 1124 1125 allApps, err := s.appRepo.ListAll(ctx, appTenant) 1126 if err != nil { 1127 return nil, errors.Wrapf(err, "while listing all applications for tenant %s", appTenant) 1128 } 1129 log.C(ctx).Debugf("Found %d existing systems", len(allApps)) 1130 1131 type key struct { 1132 name string 1133 systemNumber string 1134 } 1135 1136 uniqueNonExistingApps := make(map[key]int) 1137 keys := make([]key, 0) 1138 for index, ai := range applicationInputs { 1139 alreadyExits := false 1140 systemNumber := "" 1141 if ai.SystemNumber != nil { 1142 systemNumber = *ai.SystemNumber 1143 } 1144 aiKey := key{ 1145 name: ai.Name, 1146 systemNumber: systemNumber, 1147 } 1148 1149 if _, found := uniqueNonExistingApps[aiKey]; found { 1150 continue 1151 } 1152 1153 for _, a := range allApps { 1154 bothSystemsAreWithoutSystemNumber := (ai.SystemNumber == nil && a.SystemNumber == nil) 1155 bothSystemsHaveSystemNumber := (ai.SystemNumber != nil && a.SystemNumber != nil && *(ai.SystemNumber) == *(a.SystemNumber)) 1156 if ai.Name == a.Name && (bothSystemsAreWithoutSystemNumber || bothSystemsHaveSystemNumber) { 1157 alreadyExits = true 1158 break 1159 } 1160 } 1161 1162 if !alreadyExits { 1163 uniqueNonExistingApps[aiKey] = index 1164 keys = append(keys, aiKey) 1165 } 1166 } 1167 1168 result := make([]model.ApplicationRegisterInputWithTemplate, 0, len(uniqueNonExistingApps)) 1169 for _, key := range keys { 1170 appInputIndex := uniqueNonExistingApps[key] 1171 result = append(result, applicationInputs[appInputIndex]) 1172 } 1173 1174 return result, nil 1175 } 1176 1177 func createLabel(key string, value string, objectID string) *model.LabelInput { 1178 return &model.LabelInput{ 1179 Key: key, 1180 Value: value, 1181 ObjectID: objectID, 1182 ObjectType: model.ApplicationLabelableObject, 1183 } 1184 } 1185 1186 func (s *service) ensureIntSysExists(ctx context.Context, id *string) (bool, error) { 1187 if id == nil { 1188 return true, nil 1189 } 1190 1191 log.C(ctx).Infof("Ensuring Integration System with id %s exists", *id) 1192 exists, err := s.intSystemRepo.Exists(ctx, *id) 1193 if err != nil { 1194 return false, err 1195 } 1196 1197 if !exists { 1198 log.C(ctx).Infof("Integration System with id %s does not exist", *id) 1199 return false, nil 1200 } 1201 log.C(ctx).Infof("Integration System with id %s exists", *id) 1202 return true, nil 1203 } 1204 1205 func (s *service) getScenarioNamesForApplication(ctx context.Context, applicationID string) ([]string, error) { 1206 log.C(ctx).Infof("Getting scenarios for application with id %s", applicationID) 1207 1208 applicationLabel, err := s.GetLabel(ctx, applicationID, model.ScenariosKey) 1209 if err != nil { 1210 if apperrors.ErrorCode(err) == apperrors.NotFound { 1211 log.C(ctx).Infof("No scenarios found for application") 1212 return nil, nil 1213 } 1214 return nil, err 1215 } 1216 1217 scenarios, err := label.ValueToStringsSlice(applicationLabel.Value) 1218 if err != nil { 1219 return nil, errors.Wrapf(err, "while parsing application label values") 1220 } 1221 1222 return scenarios, nil 1223 } 1224 1225 func (s *service) getRuntimeNamesForScenarios(ctx context.Context, tenant string, scenarios []string) ([]string, error) { 1226 scenariosQuery := eventing.BuildQueryForScenarios(scenarios) 1227 runtimeScenariosFilter := []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, scenariosQuery)} 1228 1229 log.C(ctx).Debugf("Listing runtimes matching the query %s", scenariosQuery) 1230 runtimes, err := s.runtimeRepo.ListAll(ctx, tenant, runtimeScenariosFilter) 1231 if err != nil { 1232 return nil, errors.Wrapf(err, "while getting runtimes") 1233 } 1234 1235 runtimesNames := make([]string, 0, len(runtimes)) 1236 for _, r := range runtimes { 1237 runtimesNames = append(runtimesNames, r.Name) 1238 } 1239 1240 return runtimesNames, nil 1241 } 1242 1243 func (s *service) getStoredLabels(ctx context.Context, tenantID, objectID string) ([]string, error) { 1244 storedLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.ApplicationLabelableObject, objectID, model.ScenariosKey) 1245 storedLabels := make([]string, 0) 1246 if err != nil && apperrors.ErrorCode(err) != apperrors.NotFound { 1247 return nil, errors.Wrapf(err, "while getting label with id %s", objectID) 1248 } else if err == nil { 1249 if storedLabels, err = label.ValueToStringsSlice(storedLabel.Value); err != nil { 1250 return nil, errors.Wrapf(err, "while getting label with id %s", objectID) 1251 } 1252 } 1253 return storedLabels, nil 1254 } 1255 1256 func (s *service) genericUpsert(ctx context.Context, appTenant string, in model.ApplicationRegisterInput, repoUpserterFunc repoUpserterFunc) error { 1257 exists, err := s.ensureIntSysExists(ctx, in.IntegrationSystemID) 1258 if err != nil { 1259 return errors.Wrap(err, "while validating Integration System ID") 1260 } 1261 1262 if !exists { 1263 return apperrors.NewNotFoundError(resource.IntegrationSystem, *in.IntegrationSystemID) 1264 } 1265 1266 id := s.uidService.Generate() 1267 log.C(ctx).Debugf("ID %s generated for Application with name %s", id, in.Name) 1268 app := in.ToApplication(s.timestampGen(), id) 1269 1270 id, err = repoUpserterFunc(ctx, appTenant, app) 1271 if err != nil { 1272 return errors.Wrap(err, "while upserting application") 1273 } 1274 1275 app.ID = id 1276 1277 if in.Labels == nil { 1278 in.Labels = map[string]interface{}{} 1279 } 1280 in.Labels[intSysKey] = "" 1281 if in.IntegrationSystemID != nil { 1282 in.Labels[intSysKey] = *in.IntegrationSystemID 1283 } 1284 in.Labels[nameKey] = s.appNameNormalizer.Normalize(app.Name) 1285 1286 if scenarioLabel, ok := in.Labels[model.ScenariosKey]; ok { 1287 if err := s.setScenarioLabel(ctx, appTenant, &model.LabelInput{Value: scenarioLabel, ObjectID: id}); err != nil { 1288 return err 1289 } 1290 1291 // In order for the scenario label not to be attempted to be created during upsert later 1292 delete(in.Labels, model.ScenariosKey) 1293 } 1294 1295 err = s.labelService.UpsertMultipleLabels(ctx, appTenant, model.ApplicationLabelableObject, id, in.Labels) 1296 if err != nil { 1297 return errors.Wrapf(err, "while creating multiple labels for Application with id %s", id) 1298 } 1299 1300 appTypeLbl, ok := in.Labels[applicationTypeLabelKey] 1301 if !ok { 1302 log.C(ctx).Infof("Label %q is missing for %s with id %q. Skipping ord webhook creation", applicationTypeLabelKey, model.ApplicationLabelableObject, app.ID) 1303 return nil 1304 } 1305 1306 ppmsProductVersionID := "" 1307 1308 ppmsProductVersionIDLbl, ok := in.Labels[ppmsProductVersionIDLabelKey] 1309 if ppmsProductVersionIDLbl != nil && ok { 1310 if ppmsProductVersionIDValue, ok := ppmsProductVersionIDLbl.(string); ok { 1311 ppmsProductVersionID = ppmsProductVersionIDValue 1312 } 1313 } 1314 1315 ordWebhook := s.prepareORDWebhook(ctx, str.PtrStrToStr(in.BaseURL), appTypeLbl.(string), ppmsProductVersionID) 1316 if ordWebhook == nil { 1317 log.C(ctx).Infof("Skipping ORD Webhook creation for app with id %q.", app.ID) 1318 return nil 1319 } 1320 1321 if in.Webhooks == nil { 1322 in.Webhooks = []*model.WebhookInput{} 1323 } 1324 1325 in.Webhooks = append(in.Webhooks, ordWebhook) 1326 1327 if err = s.createWebhooksIfNotExist(ctx, app.ID, appTenant, in.Webhooks); err != nil { 1328 return errors.Wrapf(err, "while processing webhooks for application with id %q", app.ID) 1329 } 1330 1331 return nil 1332 } 1333 1334 func (s *service) createWebhooksIfNotExist(ctx context.Context, appID, appTenant string, appWebhooks []*model.WebhookInput) error { 1335 if len(appWebhooks) == 0 { 1336 return nil 1337 } 1338 1339 webhooksFromDB, err := s.webhookRepo.ListByReferenceObjectID(ctx, appTenant, appID, model.ApplicationWebhookReference) 1340 if err != nil { 1341 return errors.Wrapf(err, "while listig webhooks for application with id %q", appID) 1342 } 1343 1344 webhooks := make([]*model.Webhook, 0, len(appWebhooks)) 1345 for _, item := range appWebhooks { 1346 webhooks = append(webhooks, item.ToWebhook(s.uidService.Generate(), appID, model.ApplicationWebhookReference)) 1347 } 1348 1349 webhooksToCreate := make([]*model.Webhook, 0, len(appWebhooks)) 1350 for _, wh := range webhooks { 1351 found := false 1352 for _, whDB := range webhooksFromDB { 1353 if wh.Type == whDB.Type && wh.ObjectID == whDB.ObjectID && str.PtrStrToStr(wh.URL) == str.PtrStrToStr(whDB.URL) { 1354 if (wh.Auth == nil && whDB.Auth == nil) || (wh.Auth != nil && whDB.Auth != nil && str.PtrStrToStr(wh.Auth.AccessStrategy) == str.PtrStrToStr(whDB.Auth.AccessStrategy)) { 1355 found = true 1356 break 1357 } 1358 } 1359 } 1360 if !found { 1361 webhooksToCreate = append(webhooksToCreate, wh) 1362 } 1363 } 1364 1365 if len(webhooksToCreate) > 0 { 1366 if err = s.webhookRepo.CreateMany(ctx, appTenant, webhooksToCreate); err != nil { 1367 return errors.Wrapf(err, "while creating webhooks for application with id %q", appID) 1368 } 1369 } 1370 return nil 1371 } 1372 1373 func (s *service) setScenarioLabel(ctx context.Context, appTenant string, labelInput *model.LabelInput) error { 1374 inputFormations, err := label.ValueToStringsSlice(labelInput.Value) 1375 if err != nil { 1376 return errors.Wrapf(err, "while parsing formations from input label value") 1377 } 1378 1379 inputFormationsMap := createMapFromFormationsSlice(inputFormations) 1380 1381 storedLabels, err := s.getStoredLabels(ctx, appTenant, labelInput.ObjectID) 1382 if err != nil { 1383 return errors.Wrapf(err, "while getting stored labels for label with id %s", labelInput.ObjectID) 1384 } 1385 1386 storedFormationsMap := createMapFromFormationsSlice(storedLabels) 1387 assignFormationCriteria := func(formation string) bool { 1388 _, ok := storedFormationsMap[formation] 1389 return !ok 1390 } 1391 if err = s.assignFormations(ctx, appTenant, labelInput.ObjectID, inputFormations, assignFormationCriteria); err != nil { 1392 return errors.Wrapf(err, "while assigning formations") 1393 } 1394 1395 unassignFormationCriteria := func(formation string) bool { 1396 _, ok := inputFormationsMap[formation] 1397 return !ok 1398 } 1399 1400 if err = s.unassignFormations(ctx, appTenant, labelInput.ObjectID, storedLabels, unassignFormationCriteria); err != nil { 1401 return errors.Wrapf(err, "while unnasigning formations") 1402 } 1403 1404 return nil 1405 } 1406 1407 func (s *service) assignFormations(ctx context.Context, appTenant, objectID string, formations []string, shouldAssignCriteria func(string) bool) error { 1408 for _, f := range formations { 1409 if shouldAssignCriteria(f) { 1410 if _, err := s.formationService.AssignFormation(ctx, appTenant, objectID, graphql.FormationObjectTypeApplication, model.Formation{Name: f}); err != nil { 1411 return errors.Wrapf(err, "while assigning formation with name %q from application with id %q", f, objectID) 1412 } 1413 } 1414 } 1415 return nil 1416 } 1417 1418 func (s *service) unassignFormations(ctx context.Context, appTenant, objectID string, formations []string, shouldUnassignCriteria func(string) bool) error { 1419 for _, f := range formations { 1420 if shouldUnassignCriteria(f) { 1421 if _, err := s.formationService.UnassignFormation(ctx, appTenant, objectID, graphql.FormationObjectTypeApplication, model.Formation{Name: f}); err != nil { 1422 return errors.Wrapf(err, "while unassigning formation with name %q from application with id %q", f, objectID) 1423 } 1424 } 1425 } 1426 return nil 1427 } 1428 1429 func (s *service) getMappingORDConfiguration(applicationType string) (ORDWebhookMapping, bool) { 1430 for _, wm := range s.ordWebhookMapping { 1431 if wm.Type == applicationType { 1432 return wm, true 1433 } 1434 } 1435 return ORDWebhookMapping{}, false 1436 } 1437 1438 func (s *service) prepareORDWebhook(ctx context.Context, baseURL, applicationType, ppmsProductVersionID string) *model.WebhookInput { 1439 if baseURL == "" { 1440 log.C(ctx).Infof("No baseURL found in input. Will not create a webhook") 1441 return nil 1442 } 1443 1444 mappingCfg, ok := s.getMappingORDConfiguration(applicationType) 1445 if !ok { 1446 log.C(ctx).Infof("Missing ord configuration for application type %q", applicationType) 1447 return nil 1448 } 1449 1450 if ppmsProductVersionID != "" && !isPpmsProductVersionPresentInConfig(ppmsProductVersionID, mappingCfg) { 1451 log.C(ctx).Infof("Product with ppms ID %q is not supported", ppmsProductVersionID) 1452 return nil 1453 } 1454 1455 webhookInput, err := createORDWebhookInput(baseURL, mappingCfg.SubdomainSuffix, mappingCfg.OrdURLPath) 1456 if err != nil { 1457 log.C(ctx).Infof("Creating ORD Webhook failed with error: %v", err) 1458 return nil 1459 } 1460 1461 return webhookInput 1462 } 1463 1464 func isPpmsProductVersionPresentInConfig(ppmsProductVersionID string, mappingCfg ORDWebhookMapping) bool { 1465 for _, productVersion := range mappingCfg.PpmsProductVersions { 1466 if productVersion == ppmsProductVersionID { 1467 return true 1468 } 1469 } 1470 1471 return false 1472 } 1473 1474 func buildWebhookURL(suffix string, ordPath string, baseURL *string) (string, error) { 1475 url, err := url.Parse(*baseURL) 1476 if err != nil { 1477 return "", err 1478 } 1479 1480 hostParts := strings.Split(url.Host, urlSubdomainSeparator) 1481 1482 if !strings.HasSuffix(hostParts[0], suffix) { 1483 hostParts[0] = fmt.Sprintf("%s%s", hostParts[0], suffix) 1484 } 1485 1486 url.Host = strings.Join(hostParts, urlSubdomainSeparator) 1487 1488 urlStr := strings.TrimSuffix(url.String(), urlSuffixToBeTrimmed) 1489 1490 return fmt.Sprintf("%s%s", urlStr, ordPath), nil 1491 } 1492 1493 func createORDWebhookInput(baseURL, suffix, ordPath string) (*model.WebhookInput, error) { 1494 webhookURL, err := buildWebhookURL(suffix, ordPath, &baseURL) 1495 if err != nil { 1496 return nil, err 1497 } 1498 1499 return &model.WebhookInput{ 1500 Type: model.WebhookTypeOpenResourceDiscovery, 1501 URL: str.Ptr(webhookURL), 1502 Auth: &model.AuthInput{ 1503 AccessStrategy: str.Ptr(string(accessstrategy.CMPmTLSAccessStrategy)), 1504 }, 1505 }, nil 1506 } 1507 1508 func createMapFromFormationsSlice(formations []string) map[string]struct{} { 1509 resultMap := make(map[string]struct{}, len(formations)) 1510 for _, f := range formations { 1511 resultMap[f] = struct{}{} 1512 } 1513 return resultMap 1514 } 1515 1516 func allowAllCriteria(_ string) bool { 1517 return true 1518 }