github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/open_resource_discovery/service.go (about) 1 package ord 2 3 import ( 4 "context" 5 "encoding/json" 6 "strconv" 7 "strings" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/imdario/mergo" 13 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 14 15 "github.com/kyma-incubator/compass/components/director/pkg/accessstrategy" 16 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 17 18 "github.com/google/uuid" 19 "github.com/kyma-incubator/compass/components/director/internal/metrics" 20 directorresource "github.com/kyma-incubator/compass/components/director/pkg/resource" 21 22 "github.com/kyma-incubator/compass/components/director/pkg/str" 23 24 "github.com/tidwall/gjson" 25 26 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 27 "github.com/kyma-incubator/compass/components/director/internal/model" 28 "github.com/kyma-incubator/compass/components/director/pkg/log" 29 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 30 "github.com/pkg/errors" 31 ) 32 33 const ( 34 // MultiErrorSeparator represents the separator for splitting multi error into slice of validation errors 35 MultiErrorSeparator string = "* " 36 // TenantMappingCustomTypeIdentifier represents an identifier for tenant mapping webhooks in Credential exchange strategies 37 TenantMappingCustomTypeIdentifier = "sap.ucl:tenant-mapping" 38 39 customTypeProperty = "customType" 40 callbackURLProperty = "callbackUrl" 41 ) 42 43 // ServiceConfig contains configuration for the ORD aggregator service 44 type ServiceConfig struct { 45 maxParallelWebhookProcessors int 46 maxParallelSpecificationProcessors int 47 ordWebhookPartialProcessMaxDays int 48 ordWebhookPartialProcessURL string 49 ordWebhookPartialProcessing bool 50 51 credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping 52 } 53 54 // CredentialExchangeStrategyTenantMapping contains tenant mappings configuration 55 type CredentialExchangeStrategyTenantMapping struct { 56 Mode model.WebhookMode 57 Version string 58 } 59 60 type ordFetchRequest struct { 61 *model.FetchRequest 62 refObjectOrdID string 63 } 64 65 type fetchRequestResult struct { 66 fetchRequest *model.FetchRequest 67 data *string 68 status *model.FetchRequestStatus 69 } 70 71 // MetricsConfig is the ord aggregator configuration for pushing metrics to Prometheus 72 type MetricsConfig struct { 73 PushEndpoint string `envconfig:"optional,APP_METRICS_PUSH_ENDPOINT"` 74 ClientTimeout time.Duration `envconfig:"default=60s"` 75 JobName string `envconfig:"default=compass-ord-aggregator"` 76 } 77 78 // NewServiceConfig creates new ServiceConfig from the supplied parameters 79 func NewServiceConfig(maxParallelWebhookProcessors, maxParallelSpecificationProcessors, ordWebhookPartialProcessMaxDays int, ordWebhookPartialProcessURL string, ordWebhookPartialProcessing bool, credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping) ServiceConfig { 80 return ServiceConfig{ 81 maxParallelWebhookProcessors: maxParallelWebhookProcessors, 82 maxParallelSpecificationProcessors: maxParallelSpecificationProcessors, 83 ordWebhookPartialProcessMaxDays: ordWebhookPartialProcessMaxDays, 84 ordWebhookPartialProcessURL: ordWebhookPartialProcessURL, 85 ordWebhookPartialProcessing: ordWebhookPartialProcessing, 86 credentialExchangeStrategyTenantMappings: credentialExchangeStrategyTenantMappings, 87 } 88 } 89 90 // Service consists of various resource services responsible for service-layer ORD operations. 91 type Service struct { 92 config ServiceConfig 93 94 transact persistence.Transactioner 95 96 appSvc ApplicationService 97 webhookSvc WebhookService 98 bundleSvc BundleService 99 bundleReferenceSvc BundleReferenceService 100 apiSvc APIService 101 eventSvc EventService 102 specSvc SpecService 103 fetchReqSvc FetchRequestService 104 packageSvc PackageService 105 productSvc ProductService 106 vendorSvc VendorService 107 tombstoneSvc TombstoneService 108 tenantSvc TenantService 109 appTemplateVersionSvc ApplicationTemplateVersionService 110 appTemplateSvc ApplicationTemplateService 111 112 webhookConverter WebhookConverter 113 114 globalRegistrySvc GlobalRegistryService 115 ordClient Client 116 } 117 118 // NewAggregatorService returns a new object responsible for service-layer ORD operations. 119 func NewAggregatorService(config ServiceConfig, transact persistence.Transactioner, appSvc ApplicationService, webhookSvc WebhookService, bundleSvc BundleService, bundleReferenceSvc BundleReferenceService, apiSvc APIService, eventSvc EventService, specSvc SpecService, fetchReqSvc FetchRequestService, packageSvc PackageService, productSvc ProductService, vendorSvc VendorService, tombstoneSvc TombstoneService, tenantSvc TenantService, globalRegistrySvc GlobalRegistryService, client Client, webhookConverter WebhookConverter, appTemplateVersionSvc ApplicationTemplateVersionService, appTemplateSvc ApplicationTemplateService) *Service { 120 return &Service{ 121 config: config, 122 transact: transact, 123 appSvc: appSvc, 124 webhookSvc: webhookSvc, 125 bundleSvc: bundleSvc, 126 bundleReferenceSvc: bundleReferenceSvc, 127 apiSvc: apiSvc, 128 eventSvc: eventSvc, 129 specSvc: specSvc, 130 fetchReqSvc: fetchReqSvc, 131 packageSvc: packageSvc, 132 productSvc: productSvc, 133 vendorSvc: vendorSvc, 134 tombstoneSvc: tombstoneSvc, 135 tenantSvc: tenantSvc, 136 globalRegistrySvc: globalRegistrySvc, 137 ordClient: client, 138 webhookConverter: webhookConverter, 139 appTemplateVersionSvc: appTemplateVersionSvc, 140 appTemplateSvc: appTemplateSvc, 141 } 142 } 143 144 // SyncORDDocuments performs resync of ORD information provided via ORD documents for each application 145 func (s *Service) SyncORDDocuments(ctx context.Context, cfg MetricsConfig) error { 146 globalResourcesOrdIDs := s.retrieveGlobalResources(ctx) 147 ordWebhooks, err := s.getWebhooksWithOrdType(ctx) 148 if err != nil { 149 return err 150 } 151 152 queue := make(chan *model.Webhook) 153 var webhookErrors = int32(0) 154 155 workers := s.config.maxParallelWebhookProcessors 156 wg := &sync.WaitGroup{} 157 wg.Add(workers) 158 159 log.C(ctx).Infof("Starting %d parallel webhook processor workers...", workers) 160 for i := 0; i < workers; i++ { 161 go func() { 162 defer wg.Done() 163 164 for webhook := range queue { 165 entry := log.C(ctx) 166 entry = entry.WithField(log.FieldRequestID, uuid.New().String()) 167 ctx = log.ContextWithLogger(ctx, entry) 168 169 if err := s.processWebhook(ctx, cfg, webhook, globalResourcesOrdIDs); err != nil { 170 log.C(ctx).WithError(err).Errorf("error while processing webhook %q", webhook.ID) 171 atomic.AddInt32(&webhookErrors, 1) 172 } 173 } 174 }() 175 } 176 177 if s.config.ordWebhookPartialProcessing { 178 log.C(ctx).Infof("Partial ord webhook processing is enabled for URL [%s] and max days [%d]", s.config.ordWebhookPartialProcessURL, s.config.ordWebhookPartialProcessMaxDays) 179 } 180 date := time.Now().AddDate(0, 0, -1*s.config.ordWebhookPartialProcessMaxDays) 181 for _, webhook := range ordWebhooks { 182 webhookURL := str.PtrStrToStr(webhook.URL) 183 if s.config.ordWebhookPartialProcessing && strings.Contains(webhookURL, s.config.ordWebhookPartialProcessURL) { 184 if webhook.CreatedAt == nil || webhook.CreatedAt.After(date) { 185 queue <- webhook 186 } 187 } else { 188 queue <- webhook 189 } 190 } 191 close(queue) 192 wg.Wait() 193 194 if webhookErrors != 0 { 195 log.C(ctx).Errorf("failed to process %d webhooks", webhookErrors) 196 } 197 198 return nil 199 } 200 201 // ProcessApplications performs resync of ORD information provided via ORD documents for list of applications 202 func (s *Service) ProcessApplications(ctx context.Context, cfg MetricsConfig, appIDs []string) error { 203 if len(appIDs) == 0 { 204 return nil 205 } 206 207 globalResourcesOrdIDs := s.retrieveGlobalResources(ctx) 208 for _, appID := range appIDs { 209 if err := s.processApplication(ctx, cfg, globalResourcesOrdIDs, appID); err != nil { 210 return errors.Wrapf(err, "processing of ORD data for application with id %q failed", appID) 211 } 212 } 213 return nil 214 } 215 216 func (s *Service) processApplication(ctx context.Context, cfg MetricsConfig, globalResourcesOrdIDs map[string]bool, appID string) error { 217 webhooks, err := s.getWebhooksForApplication(ctx, appID) 218 if err != nil { 219 return errors.Wrapf(err, "retrieving of webhooks for application with id %q failed", appID) 220 } 221 222 for _, wh := range webhooks { 223 if wh.Type == model.WebhookTypeOpenResourceDiscovery && wh.URL != nil { 224 if err := s.processApplicationWebhook(ctx, cfg, wh, appID, globalResourcesOrdIDs); err != nil { 225 return errors.Wrapf(err, "processing of ORD webhook for application with id %q failed", appID) 226 } 227 } 228 } 229 return nil 230 } 231 232 // ProcessApplicationTemplates performs resync of ORD information provided via ORD documents for list of application templates 233 func (s *Service) ProcessApplicationTemplates(ctx context.Context, cfg MetricsConfig, appTemplateIDs []string) error { 234 if len(appTemplateIDs) == 0 { 235 return nil 236 } 237 238 globalResourcesOrdIDs := s.retrieveGlobalResources(ctx) 239 for _, appTemplateID := range appTemplateIDs { 240 if err := s.processApplicationTemplate(ctx, cfg, globalResourcesOrdIDs, appTemplateID); err != nil { 241 return errors.Wrapf(err, "processing of ORD data for application template with id %q failed", appTemplateID) 242 } 243 } 244 return nil 245 } 246 247 func (s *Service) processApplicationTemplate(ctx context.Context, cfg MetricsConfig, globalResourcesOrdIDs map[string]bool, appTemplateID string) error { 248 webhooks, err := s.getWebhooksForApplicationTemplate(ctx, appTemplateID) 249 if err != nil { 250 return errors.Wrapf(err, "retrieving of webhooks for application template with id %q failed", appTemplateID) 251 } 252 253 for _, wh := range webhooks { 254 if wh.Type == model.WebhookTypeOpenResourceDiscovery && wh.URL != nil { 255 if err := s.processApplicationTemplateWebhook(ctx, cfg, wh, appTemplateID, globalResourcesOrdIDs); err != nil { 256 return err 257 } 258 259 apps, err := s.getApplicationsForAppTemplate(ctx, appTemplateID) 260 if err != nil { 261 return errors.Wrapf(err, "retrieving of applications for application template with id %q failed", appTemplateID) 262 } 263 264 for _, app := range apps { 265 if err := s.processApplicationWebhook(ctx, cfg, wh, app.ID, globalResourcesOrdIDs); err != nil { 266 return errors.Wrapf(err, "processing of ORD webhook for application with id %q failed", app.ID) 267 } 268 } 269 } 270 } 271 return nil 272 } 273 274 func (s *Service) processWebhook(ctx context.Context, cfg MetricsConfig, webhook *model.Webhook, globalResourcesOrdIDs map[string]bool) error { 275 if webhook.ObjectType == model.ApplicationTemplateWebhookReference { 276 appTemplateID := webhook.ObjectID 277 278 if err := s.processApplicationTemplateWebhook(ctx, cfg, webhook, appTemplateID, globalResourcesOrdIDs); err != nil { 279 return err 280 } 281 282 apps, err := s.getApplicationsForAppTemplate(ctx, appTemplateID) 283 if err != nil { 284 return err 285 } 286 287 for _, app := range apps { 288 if err = s.processApplicationWebhook(ctx, cfg, webhook, app.ID, globalResourcesOrdIDs); err != nil { 289 return err 290 } 291 } 292 } else if webhook.ObjectType == model.ApplicationWebhookReference { 293 appID := webhook.ObjectID 294 if err := s.processApplicationWebhook(ctx, cfg, webhook, appID, globalResourcesOrdIDs); err != nil { 295 return err 296 } 297 } 298 299 return nil 300 } 301 302 func (s *Service) retrieveGlobalResources(ctx context.Context) map[string]bool { 303 globalResourcesOrdIDs, err := s.globalRegistrySvc.SyncGlobalResources(ctx) 304 if err != nil { 305 log.C(ctx).WithError(err).Errorf("Error while synchronizing global resources: %s. Proceeding with already existing global resources...", err) 306 globalResourcesOrdIDs, err = s.globalRegistrySvc.ListGlobalResources(ctx) 307 if err != nil { 308 log.C(ctx).WithError(err).Errorf("Error while listing existing global resource: %s. Proceeding with empty globalResourceOrdIDs... Validation of Documents relying on global resources might fail.", err) 309 } 310 } 311 312 if globalResourcesOrdIDs == nil { 313 globalResourcesOrdIDs = make(map[string]bool) 314 } 315 return globalResourcesOrdIDs 316 } 317 318 func (s *Service) getWebhooksForApplicationTemplate(ctx context.Context, appTemplateID string) ([]*model.Webhook, error) { 319 tx, err := s.transact.Begin() 320 if err != nil { 321 return nil, err 322 } 323 defer s.transact.RollbackUnlessCommitted(ctx, tx) 324 325 ctx = persistence.SaveToContext(ctx, tx) 326 ordWebhooks, err := s.webhookSvc.ListForApplicationTemplate(ctx, appTemplateID) 327 if err != nil { 328 log.C(ctx).WithError(err).Errorf("error while fetching webhooks for application template with id %s", appTemplateID) 329 return nil, err 330 } 331 332 if err := tx.Commit(); err != nil { 333 return nil, err 334 } 335 336 return ordWebhooks, nil 337 } 338 339 func (s *Service) getWebhooksForApplication(ctx context.Context, appID string) ([]*model.Webhook, error) { 340 tx, err := s.transact.Begin() 341 if err != nil { 342 return nil, err 343 } 344 defer s.transact.RollbackUnlessCommitted(ctx, tx) 345 346 ctx = persistence.SaveToContext(ctx, tx) 347 ordWebhooks, err := s.webhookSvc.ListForApplication(ctx, appID) 348 if err != nil { 349 log.C(ctx).WithError(err).Errorf("error while fetching webhooks for application with id %s", appID) 350 return nil, err 351 } 352 353 if err := tx.Commit(); err != nil { 354 return nil, err 355 } 356 357 return ordWebhooks, nil 358 } 359 360 func (s *Service) processDocuments(ctx context.Context, resource Resource, baseURL string, documents Documents, globalResourcesOrdIDs map[string]bool, validationErrors *error) error { 361 if _, err := s.processDescribedSystemVersions(ctx, resource, documents); err != nil { 362 return err 363 } 364 365 resourcesFromDB, err := s.fetchResources(ctx, resource, documents) 366 if err != nil { 367 return err 368 } 369 370 resourceHashes, err := hashResources(documents) 371 if err != nil { 372 return err 373 } 374 375 validationResult := documents.Validate(baseURL, resourcesFromDB, resourceHashes, globalResourcesOrdIDs, s.config.credentialExchangeStrategyTenantMappings) 376 if validationResult != nil { 377 validationResult = &ORDDocumentValidationError{errors.Wrap(validationResult, "invalid documents")} 378 *validationErrors = validationResult 379 } 380 381 if err := documents.Sanitize(baseURL); err != nil { 382 return errors.Wrap(err, "while sanitizing ORD documents") 383 } 384 385 vendorsInput := make([]*model.VendorInput, 0) 386 productsInput := make([]*model.ProductInput, 0) 387 packagesInput := make([]*model.PackageInput, 0) 388 bundlesInput := make([]*model.BundleCreateInput, 0) 389 apisInput := make([]*model.APIDefinitionInput, 0) 390 eventsInput := make([]*model.EventDefinitionInput, 0) 391 tombstonesInput := make([]*model.TombstoneInput, 0) 392 for _, doc := range documents { 393 vendorsInput = append(vendorsInput, doc.Vendors...) 394 productsInput = append(productsInput, doc.Products...) 395 packagesInput = append(packagesInput, doc.Packages...) 396 bundlesInput = append(bundlesInput, doc.ConsumptionBundles...) 397 apisInput = append(apisInput, doc.APIResources...) 398 eventsInput = append(eventsInput, doc.EventResources...) 399 tombstonesInput = append(tombstonesInput, doc.Tombstones...) 400 } 401 402 ordLocalID := s.getUniqueLocalTenantID(documents) 403 if ordLocalID != "" && resource.LocalTenantID == nil { 404 if err := s.appSvc.Update(ctx, resource.ID, model.ApplicationUpdateInput{LocalTenantID: str.Ptr(ordLocalID)}); err != nil { 405 return err 406 } 407 } 408 for _, doc := range documents { 409 if resource.Type == directorresource.ApplicationTemplate && doc.DescribedSystemVersion == nil { 410 continue 411 } 412 413 resourceToAggregate := Resource{ 414 ID: resource.ID, 415 Type: directorresource.Application, 416 } 417 418 if doc.DescribedSystemVersion != nil { 419 applicationTemplateID := resource.ID 420 if resource.Type == directorresource.Application && resource.ParentID != nil { 421 applicationTemplateID = *resource.ParentID 422 } 423 424 appTemplateVersion, err := s.getApplicationTemplateVersionByAppTemplateIDAndVersionInTx(ctx, applicationTemplateID, doc.DescribedSystemVersion.Version) 425 if err != nil { 426 return err 427 } 428 429 resourceToAggregate = Resource{ 430 ID: appTemplateVersion.ID, 431 Type: directorresource.ApplicationTemplateVersion, 432 } 433 } 434 435 vendorsFromDB, err := s.processVendors(ctx, resourceToAggregate.Type, resourceToAggregate.ID, doc.Vendors) 436 if err != nil { 437 return err 438 } 439 440 productsFromDB, err := s.processProducts(ctx, resourceToAggregate.Type, resourceToAggregate.ID, doc.Products) 441 if err != nil { 442 return err 443 } 444 445 packagesFromDB, err := s.processPackages(ctx, resourceToAggregate.Type, resourceToAggregate.ID, doc.Packages, resourceHashes) 446 if err != nil { 447 return err 448 } 449 450 bundlesFromDB, err := s.processBundles(ctx, resourceToAggregate.Type, resourceToAggregate.ID, doc.ConsumptionBundles, resourceHashes) 451 if err != nil { 452 return err 453 } 454 455 apisFromDB, apiFetchRequests, err := s.processAPIs(ctx, resourceToAggregate.Type, resourceToAggregate.ID, bundlesFromDB, packagesFromDB, doc.APIResources, resourceHashes) 456 if err != nil { 457 return err 458 } 459 460 eventsFromDB, eventFetchRequests, err := s.processEvents(ctx, resourceToAggregate.Type, resourceToAggregate.ID, bundlesFromDB, packagesFromDB, doc.EventResources, resourceHashes) 461 if err != nil { 462 return err 463 } 464 465 tombstonesFromDB, err := s.processTombstones(ctx, resourceToAggregate.Type, resourceToAggregate.ID, doc.Tombstones) 466 if err != nil { 467 return err 468 } 469 470 fetchRequests := append(apiFetchRequests, eventFetchRequests...) 471 fetchRequests, err = s.deleteTombstonedResources(ctx, resourceToAggregate.Type, vendorsFromDB, productsFromDB, packagesFromDB, bundlesFromDB, apisFromDB, eventsFromDB, tombstonesFromDB, fetchRequests) 472 if err != nil { 473 return err 474 } 475 476 if err := s.processSpecs(ctx, resourceToAggregate.Type, fetchRequests); err != nil { 477 return err 478 } 479 } 480 481 return nil 482 } 483 484 func (s *Service) processSpecs(ctx context.Context, resourceType directorresource.Type, ordFetchRequests []*ordFetchRequest) error { 485 queue := make(chan *model.FetchRequest) 486 487 workers := s.config.maxParallelSpecificationProcessors 488 wg := &sync.WaitGroup{} 489 wg.Add(workers) 490 491 fetchReqMutex := sync.Mutex{} 492 fetchRequestResults := make([]*fetchRequestResult, 0) 493 log.C(ctx).Infof("Starting %d parallel specification processor workers to process %d fetch requests...", workers, len(ordFetchRequests)) 494 for i := 0; i < workers; i++ { 495 go func() { 496 defer wg.Done() 497 498 for fetchRequest := range queue { 499 fr := *fetchRequest 500 ctx = addFieldToLogger(ctx, "fetch_request_id", fr.ID) 501 log.C(ctx).Infof("Will attempt to execute spec fetch request for spec with id %q and spec entity type %q", fr.ObjectID, fr.ObjectType) 502 data, status := s.fetchReqSvc.FetchSpec(ctx, &fr) 503 log.C(ctx).Infof("Finished executing spec fetch request for spec with id %q and spec entity type %q with result: %s. Adding to result queue...", fr.ObjectID, fr.ObjectType, status.Condition) 504 s.addFetchRequestResult(&fetchRequestResults, &fetchRequestResult{ 505 fetchRequest: &fr, 506 data: data, 507 status: status, 508 }, &fetchReqMutex) 509 } 510 }() 511 } 512 513 for _, ordFetchRequest := range ordFetchRequests { 514 queue <- ordFetchRequest.FetchRequest 515 } 516 close(queue) 517 wg.Wait() 518 519 if err := s.processFetchRequestResults(ctx, resourceType, fetchRequestResults); err != nil { 520 errMsg := "error while processing fetch request results" 521 log.C(ctx).WithError(err).Error(errMsg) 522 return errors.Errorf(errMsg) 523 } else { 524 log.C(ctx).Info("Successfully processed fetch request results") 525 } 526 527 fetchRequestErrors := 0 528 for _, fr := range fetchRequestResults { 529 if fr.status.Condition == model.FetchRequestStatusConditionFailed { 530 fetchRequestErrors += 1 531 } 532 } 533 534 if fetchRequestErrors != 0 { 535 return errors.Errorf("failed to process %d specification fetch requests", fetchRequestErrors) 536 } 537 538 return nil 539 } 540 541 func (s *Service) addFetchRequestResult(fetchReqResults *[]*fetchRequestResult, result *fetchRequestResult, mutex *sync.Mutex) { 542 mutex.Lock() 543 defer mutex.Unlock() 544 *fetchReqResults = append(*fetchReqResults, result) 545 } 546 547 func (s *Service) processFetchRequestResults(ctx context.Context, resourceType directorresource.Type, results []*fetchRequestResult) error { 548 tx, err := s.transact.Begin() 549 if err != nil { 550 log.C(ctx).WithError(err).Errorf("error while opening transaction to process fetch request results") 551 return err 552 } 553 defer s.transact.RollbackUnlessCommitted(ctx, tx) 554 ctx = persistence.SaveToContext(ctx, tx) 555 556 for _, result := range results { 557 if resourceType.IsTenantIgnorable() { 558 err = s.processFetchRequestResultGlobal(ctx, result) 559 } else { 560 err = s.processFetchRequestResult(ctx, result) 561 } 562 if err != nil { 563 return err 564 } 565 } 566 567 return tx.Commit() 568 } 569 570 func (s *Service) processFetchRequestResult(ctx context.Context, result *fetchRequestResult) error { 571 specReferenceType := model.APISpecReference 572 if result.fetchRequest.ObjectType == model.EventSpecFetchRequestReference { 573 specReferenceType = model.EventSpecReference 574 } 575 576 if result.status.Condition == model.FetchRequestStatusConditionSucceeded { 577 spec, err := s.specSvc.GetByID(ctx, result.fetchRequest.ObjectID, specReferenceType) 578 if err != nil { 579 return err 580 } 581 582 spec.Data = result.data 583 584 if err = s.specSvc.UpdateSpecOnly(ctx, *spec); err != nil { 585 return err 586 } 587 } 588 589 result.fetchRequest.Status = result.status 590 return s.fetchReqSvc.Update(ctx, result.fetchRequest) 591 } 592 593 func (s *Service) processFetchRequestResultGlobal(ctx context.Context, result *fetchRequestResult) error { 594 if result.status.Condition == model.FetchRequestStatusConditionSucceeded { 595 spec, err := s.specSvc.GetByIDGlobal(ctx, result.fetchRequest.ObjectID) 596 if err != nil { 597 return err 598 } 599 600 spec.Data = result.data 601 602 if err = s.specSvc.UpdateSpecOnlyGlobal(ctx, *spec); err != nil { 603 return err 604 } 605 } 606 607 result.fetchRequest.Status = result.status 608 return s.fetchReqSvc.UpdateGlobal(ctx, result.fetchRequest) 609 } 610 611 func (s *Service) deleteTombstonedResources(ctx context.Context, resourceType directorresource.Type, vendorsFromDB []*model.Vendor, productsFromDB []*model.Product, packagesFromDB []*model.Package, bundlesFromDB []*model.Bundle, apisFromDB []*model.APIDefinition, eventsFromDB []*model.EventDefinition, tombstonesFromDB []*model.Tombstone, fetchRequests []*ordFetchRequest) ([]*ordFetchRequest, error) { 612 tx, err := s.transact.Begin() 613 if err != nil { 614 return nil, err 615 } 616 defer s.transact.RollbackUnlessCommitted(ctx, tx) 617 618 ctx = persistence.SaveToContext(ctx, tx) 619 620 frIdxToExclude := make([]int, 0) 621 for _, ts := range tombstonesFromDB { 622 if i, found := searchInSlice(len(packagesFromDB), func(i int) bool { 623 return packagesFromDB[i].OrdID == ts.OrdID 624 }); found { 625 if err := s.packageSvc.Delete(ctx, resourceType, packagesFromDB[i].ID); err != nil { 626 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 627 } 628 } 629 if i, found := searchInSlice(len(apisFromDB), func(i int) bool { 630 return equalStrings(apisFromDB[i].OrdID, &ts.OrdID) 631 }); found { 632 if err := s.apiSvc.Delete(ctx, resourceType, apisFromDB[i].ID); err != nil { 633 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 634 } 635 } 636 if i, found := searchInSlice(len(eventsFromDB), func(i int) bool { 637 return equalStrings(eventsFromDB[i].OrdID, &ts.OrdID) 638 }); found { 639 if err := s.eventSvc.Delete(ctx, resourceType, eventsFromDB[i].ID); err != nil { 640 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 641 } 642 } 643 if i, found := searchInSlice(len(bundlesFromDB), func(i int) bool { 644 return equalStrings(bundlesFromDB[i].OrdID, &ts.OrdID) 645 }); found { 646 if err := s.bundleSvc.Delete(ctx, resourceType, bundlesFromDB[i].ID); err != nil { 647 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 648 } 649 } 650 if i, found := searchInSlice(len(vendorsFromDB), func(i int) bool { 651 return vendorsFromDB[i].OrdID == ts.OrdID 652 }); found { 653 if err := s.vendorSvc.Delete(ctx, resourceType, vendorsFromDB[i].ID); err != nil { 654 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 655 } 656 } 657 if i, found := searchInSlice(len(productsFromDB), func(i int) bool { 658 return productsFromDB[i].OrdID == ts.OrdID 659 }); found { 660 if err := s.productSvc.Delete(ctx, resourceType, productsFromDB[i].ID); err != nil { 661 return nil, errors.Wrapf(err, "error while deleting resource with ORD ID %q based on its tombstone", ts.OrdID) 662 } 663 } 664 for i := range fetchRequests { 665 if equalStrings(&fetchRequests[i].refObjectOrdID, &ts.OrdID) { 666 frIdxToExclude = append(frIdxToExclude, i) 667 } 668 } 669 } 670 671 return excludeUnnecessaryFetchRequests(fetchRequests, frIdxToExclude), tx.Commit() 672 } 673 674 func (s *Service) processDescribedSystemVersions(ctx context.Context, resource Resource, documents Documents) ([]*model.ApplicationTemplateVersion, error) { 675 appTemplateID := resource.ID 676 if resource.Type == directorresource.Application && resource.ParentID != nil { 677 appTemplateID = *resource.ParentID 678 } 679 680 appTemplateVersions, err := s.listApplicationTemplateVersionByAppTemplateIDInTx(ctx, appTemplateID) 681 if err != nil && !apperrors.IsNotFoundError(err) { 682 return nil, err 683 } 684 685 for _, document := range documents { 686 if document.DescribedSystemVersion == nil { 687 continue 688 } 689 690 if err = s.resyncApplicationTemplateVersionInTx(ctx, appTemplateID, appTemplateVersions, document.DescribedSystemVersion); err != nil { 691 return nil, err 692 } 693 } 694 695 return s.listApplicationTemplateVersionByAppTemplateIDInTx(ctx, appTemplateID) 696 } 697 698 func (s *Service) listApplicationTemplateVersionByAppTemplateIDInTx(ctx context.Context, applicationTemplateID string) ([]*model.ApplicationTemplateVersion, error) { 699 tx, err := s.transact.Begin() 700 if err != nil { 701 return nil, err 702 } 703 defer s.transact.RollbackUnlessCommitted(ctx, tx) 704 ctx = persistence.SaveToContext(ctx, tx) 705 706 appTemplateVersions, err := s.appTemplateVersionSvc.ListByAppTemplateID(ctx, applicationTemplateID) 707 if err != nil { 708 return nil, err 709 } 710 711 return appTemplateVersions, tx.Commit() 712 } 713 714 func (s *Service) getApplicationTemplateVersionByAppTemplateIDAndVersionInTx(ctx context.Context, applicationTemplateID, version string) (*model.ApplicationTemplateVersion, error) { 715 tx, err := s.transact.Begin() 716 if err != nil { 717 return nil, err 718 } 719 defer s.transact.RollbackUnlessCommitted(ctx, tx) 720 ctx = persistence.SaveToContext(ctx, tx) 721 722 systemVersion, err := s.appTemplateVersionSvc.GetByAppTemplateIDAndVersion(ctx, applicationTemplateID, version) 723 if err != nil { 724 return nil, err 725 } 726 727 return systemVersion, tx.Commit() 728 } 729 730 func (s *Service) processVendors(ctx context.Context, resourceType directorresource.Type, resourceID string, vendors []*model.VendorInput) ([]*model.Vendor, error) { 731 vendorsFromDB, err := s.listVendorsInTx(ctx, resourceType, resourceID) 732 if err != nil { 733 return nil, err 734 } 735 736 for _, vendor := range vendors { 737 if err := s.resyncVendorInTx(ctx, resourceType, resourceID, vendorsFromDB, vendor); err != nil { 738 return nil, err 739 } 740 } 741 742 vendorsFromDB, err = s.listVendorsInTx(ctx, resourceType, resourceID) 743 if err != nil { 744 return nil, err 745 } 746 return vendorsFromDB, nil 747 } 748 749 func (s *Service) listVendorsInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.Vendor, error) { 750 tx, err := s.transact.Begin() 751 if err != nil { 752 return nil, err 753 } 754 defer s.transact.RollbackUnlessCommitted(ctx, tx) 755 ctx = persistence.SaveToContext(ctx, tx) 756 757 var vendorsFromDB []*model.Vendor 758 if resourceType == directorresource.Application { 759 vendorsFromDB, err = s.vendorSvc.ListByApplicationID(ctx, resourceID) 760 } else if resourceType == directorresource.ApplicationTemplateVersion { 761 vendorsFromDB, err = s.vendorSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 762 } 763 if err != nil { 764 return nil, errors.Wrapf(err, "error while listing vendors for %s with id %q", resourceType, resourceID) 765 } 766 767 return vendorsFromDB, tx.Commit() 768 } 769 770 func (s *Service) resyncVendorInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, vendorsFromDB []*model.Vendor, vendor *model.VendorInput) error { 771 tx, err := s.transact.Begin() 772 if err != nil { 773 return err 774 } 775 defer s.transact.RollbackUnlessCommitted(ctx, tx) 776 ctx = persistence.SaveToContext(ctx, tx) 777 778 if err := s.resyncVendor(ctx, resourceType, resourceID, vendorsFromDB, *vendor); err != nil { 779 return errors.Wrapf(err, "error while resyncing vendor with ORD ID %q", vendor.OrdID) 780 } 781 return tx.Commit() 782 } 783 784 func (s *Service) resyncApplicationTemplateVersionInTx(ctx context.Context, appTemplateID string, appTemplateVersionsFromDB []*model.ApplicationTemplateVersion, appTemplateVersion *model.ApplicationTemplateVersionInput) error { 785 tx, err := s.transact.Begin() 786 if err != nil { 787 return err 788 } 789 defer s.transact.RollbackUnlessCommitted(ctx, tx) 790 ctx = persistence.SaveToContext(ctx, tx) 791 792 if err = s.resyncAppTemplateVersion(ctx, appTemplateID, appTemplateVersionsFromDB, appTemplateVersion); err != nil { 793 return errors.Wrapf(err, "error while resyncing App Template Version for App template %q", appTemplateID) 794 } 795 return tx.Commit() 796 } 797 798 func (s *Service) processProducts(ctx context.Context, resourceType directorresource.Type, resourceID string, products []*model.ProductInput) ([]*model.Product, error) { 799 productsFromDB, err := s.listProductsInTx(ctx, resourceType, resourceID) 800 if err != nil { 801 return nil, err 802 } 803 804 for _, product := range products { 805 if err := s.resyncProductInTx(ctx, resourceType, resourceID, productsFromDB, product); err != nil { 806 return nil, err 807 } 808 } 809 810 productsFromDB, err = s.listProductsInTx(ctx, resourceType, resourceID) 811 if err != nil { 812 return nil, err 813 } 814 return productsFromDB, nil 815 } 816 817 func (s *Service) listProductsInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.Product, error) { 818 tx, err := s.transact.Begin() 819 if err != nil { 820 return nil, err 821 } 822 defer s.transact.RollbackUnlessCommitted(ctx, tx) 823 ctx = persistence.SaveToContext(ctx, tx) 824 825 var productsFromDB []*model.Product 826 if resourceType == directorresource.Application { 827 productsFromDB, err = s.productSvc.ListByApplicationID(ctx, resourceID) 828 } else if resourceType == directorresource.ApplicationTemplateVersion { 829 productsFromDB, err = s.productSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 830 } 831 if err != nil { 832 return nil, errors.Wrapf(err, "error while listing products for %s with id %q", resourceType, resourceID) 833 } 834 835 return productsFromDB, tx.Commit() 836 } 837 838 func (s *Service) resyncProductInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, productsFromDB []*model.Product, product *model.ProductInput) error { 839 tx, err := s.transact.Begin() 840 if err != nil { 841 return err 842 } 843 defer s.transact.RollbackUnlessCommitted(ctx, tx) 844 ctx = persistence.SaveToContext(ctx, tx) 845 846 if err := s.resyncProduct(ctx, resourceType, resourceID, productsFromDB, *product); err != nil { 847 return errors.Wrapf(err, "error while resyncing product with ORD ID %q", product.OrdID) 848 } 849 return tx.Commit() 850 } 851 852 func (s *Service) processPackages(ctx context.Context, resourceType directorresource.Type, resourceID string, packages []*model.PackageInput, resourceHashes map[string]uint64) ([]*model.Package, error) { 853 packagesFromDB, err := s.listPackagesInTx(ctx, resourceType, resourceID) 854 if err != nil { 855 return nil, err 856 } 857 858 for _, pkg := range packages { 859 pkgHash := resourceHashes[pkg.OrdID] 860 if err := s.resyncPackageInTx(ctx, resourceType, resourceID, packagesFromDB, pkg, pkgHash); err != nil { 861 return nil, err 862 } 863 } 864 865 packagesFromDB, err = s.listPackagesInTx(ctx, resourceType, resourceID) 866 if err != nil { 867 return nil, err 868 } 869 return packagesFromDB, nil 870 } 871 872 func (s *Service) listPackagesInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.Package, error) { 873 tx, err := s.transact.Begin() 874 if err != nil { 875 return nil, err 876 } 877 defer s.transact.RollbackUnlessCommitted(ctx, tx) 878 ctx = persistence.SaveToContext(ctx, tx) 879 880 var packagesFromDB []*model.Package 881 if resourceType == directorresource.Application { 882 packagesFromDB, err = s.packageSvc.ListByApplicationID(ctx, resourceID) 883 } else if resourceType == directorresource.ApplicationTemplateVersion { 884 packagesFromDB, err = s.packageSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 885 } 886 if err != nil { 887 return nil, errors.Wrapf(err, "error while listing packages for %s with id %q", resourceType, resourceID) 888 } 889 890 return packagesFromDB, tx.Commit() 891 } 892 893 func (s *Service) resyncPackageInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, packagesFromDB []*model.Package, pkg *model.PackageInput, pkgHash uint64) error { 894 tx, err := s.transact.Begin() 895 if err != nil { 896 return err 897 } 898 defer s.transact.RollbackUnlessCommitted(ctx, tx) 899 ctx = persistence.SaveToContext(ctx, tx) 900 901 if err := s.resyncPackage(ctx, resourceType, resourceID, packagesFromDB, *pkg, pkgHash); err != nil { 902 return errors.Wrapf(err, "error while resyncing package with ORD ID %q", pkg.OrdID) 903 } 904 return tx.Commit() 905 } 906 907 func (s *Service) processBundles(ctx context.Context, resourceType directorresource.Type, resourceID string, bundles []*model.BundleCreateInput, resourceHashes map[string]uint64) ([]*model.Bundle, error) { 908 bundlesFromDB, err := s.listBundlesInTx(ctx, resourceType, resourceID) 909 if err != nil { 910 return nil, err 911 } 912 913 credentialExchangeStrategyHashCurrent := uint64(0) 914 var credentialExchangeStrategyJSON gjson.Result 915 for _, bndl := range bundles { 916 bndlHash := resourceHashes[str.PtrStrToStr(bndl.OrdID)] 917 if err := s.resyncBundleInTx(ctx, resourceType, resourceID, bundlesFromDB, bndl, bndlHash); err != nil { 918 return nil, err 919 } 920 921 credentialExchangeStrategies, err := bndl.CredentialExchangeStrategies.MarshalJSON() 922 if err != nil { 923 return nil, errors.Wrapf(err, "while marshalling credential exchange strategies for %s with ID %s", resourceType, resourceID) 924 } 925 926 for _, credentialExchangeStrategy := range gjson.ParseBytes(credentialExchangeStrategies).Array() { 927 customType := credentialExchangeStrategy.Get(customTypeProperty).String() 928 isTenantMappingType := strings.Contains(customType, TenantMappingCustomTypeIdentifier) 929 930 if !isTenantMappingType { 931 continue 932 } 933 934 currentHash, err := HashObject(credentialExchangeStrategy) 935 if err != nil { 936 return nil, errors.Wrapf(err, "while hasing credential exchange strategy for application with ID %s", resourceID) 937 } 938 939 if credentialExchangeStrategyHashCurrent != 0 && currentHash != credentialExchangeStrategyHashCurrent { 940 return nil, errors.Errorf("There are differences in the Credential Exchange Strategies for Tenant Mappings for application with ID %s. They should be the same.", resourceID) 941 } 942 943 credentialExchangeStrategyHashCurrent = currentHash 944 credentialExchangeStrategyJSON = credentialExchangeStrategy 945 } 946 } 947 948 if err = s.resyncTenantMappingWebhooksInTx(ctx, credentialExchangeStrategyJSON, resourceID); err != nil { 949 return nil, err 950 } 951 952 bundlesFromDB, err = s.listBundlesInTx(ctx, resourceType, resourceID) 953 if err != nil { 954 return nil, err 955 } 956 957 return bundlesFromDB, nil 958 } 959 960 func (s *Service) listBundlesInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.Bundle, error) { 961 tx, err := s.transact.Begin() 962 if err != nil { 963 return nil, err 964 } 965 defer s.transact.RollbackUnlessCommitted(ctx, tx) 966 ctx = persistence.SaveToContext(ctx, tx) 967 968 var bundlesFromDB []*model.Bundle 969 if resourceType == directorresource.Application { 970 bundlesFromDB, err = s.bundleSvc.ListByApplicationIDNoPaging(ctx, resourceID) 971 } else if resourceType == directorresource.ApplicationTemplateVersion { 972 bundlesFromDB, err = s.bundleSvc.ListByApplicationTemplateVersionIDNoPaging(ctx, resourceID) 973 } 974 if err != nil { 975 return nil, errors.Wrapf(err, "error while listing bundles for %s with id %q", resourceType, resourceID) 976 } 977 978 return bundlesFromDB, tx.Commit() 979 } 980 981 func (s *Service) resyncBundleInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, bundlesFromDB []*model.Bundle, bundle *model.BundleCreateInput, bndlHash uint64) error { 982 tx, err := s.transact.Begin() 983 if err != nil { 984 return err 985 } 986 defer s.transact.RollbackUnlessCommitted(ctx, tx) 987 ctx = persistence.SaveToContext(ctx, tx) 988 989 if err := s.resyncBundle(ctx, resourceType, resourceID, bundlesFromDB, *bundle, bndlHash); err != nil { 990 return errors.Wrapf(err, "error while resyncing bundle with ORD ID %q", *bundle.OrdID) 991 } 992 return tx.Commit() 993 } 994 995 func (s *Service) resyncTenantMappingWebhooksInTx(ctx context.Context, credentialExchangeStrategyJSON gjson.Result, appID string) error { 996 if !credentialExchangeStrategyJSON.IsObject() { 997 log.C(ctx).Debugf("There are no tenant mappings to resync") 998 return nil 999 } 1000 1001 tenantMappingData, err := s.getTenantMappingData(credentialExchangeStrategyJSON, appID) 1002 if err != nil { 1003 return err 1004 } 1005 1006 log.C(ctx).Infof("Enriching tenant mapping webhooks for application with ID %s", appID) 1007 1008 enrichedWebhooks, err := s.webhookSvc.EnrichWebhooksWithTenantMappingWebhooks([]*graphql.WebhookInput{createWebhookInput(credentialExchangeStrategyJSON, tenantMappingData)}) 1009 if err != nil { 1010 return errors.Wrapf(err, "while enriching webhooks with tenant mapping webhooks for application with ID %s", appID) 1011 } 1012 1013 ctxWithoutTenant := context.Background() 1014 tx, err := s.transact.Begin() 1015 if err != nil { 1016 return err 1017 } 1018 defer s.transact.RollbackUnlessCommitted(ctxWithoutTenant, tx) 1019 1020 ctxWithoutTenant = persistence.SaveToContext(ctxWithoutTenant, tx) 1021 ctxWithoutTenant = tenant.SaveToContext(ctxWithoutTenant, "", "") 1022 1023 appWebhooksFromDB, err := s.webhookSvc.ListForApplicationGlobal(ctxWithoutTenant, appID) 1024 if err != nil { 1025 return errors.Wrapf(err, "while listing webhooks from application with ID %s", appID) 1026 } 1027 1028 tenantMappingRelatedWebhooksFromDB, enrichedWhModels, enrichedWhModelInputs, err := s.processEnrichedWebhooks(enrichedWebhooks, appWebhooksFromDB) 1029 if err != nil { 1030 return err 1031 } 1032 1033 isEqual, err := isWebhookDataEqual(tenantMappingRelatedWebhooksFromDB, enrichedWhModels) 1034 if err != nil { 1035 return err 1036 } 1037 1038 if isEqual { 1039 log.C(ctxWithoutTenant).Infof("There are no differences in tenant mapping webhooks from the DB and the ORD document") 1040 return tx.Commit() 1041 } 1042 1043 log.C(ctxWithoutTenant).Infof("There are differences in tenant mapping webhooks from the DB and the ORD document. Continuing the sync.") 1044 1045 if err := s.deleteWebhooks(ctxWithoutTenant, tenantMappingRelatedWebhooksFromDB, appID); err != nil { 1046 return err 1047 } 1048 1049 if err := s.createWebhooks(ctxWithoutTenant, enrichedWhModelInputs, appID); err != nil { 1050 return err 1051 } 1052 1053 return tx.Commit() 1054 } 1055 1056 func (s *Service) deleteWebhooks(ctx context.Context, webhooks []*model.Webhook, appID string) error { 1057 for _, webhook := range webhooks { 1058 log.C(ctx).Infof("Deleting webhook with ID %s for application %s", webhook.ID, appID) 1059 if err := s.webhookSvc.Delete(ctx, webhook.ID, webhook.ObjectType); err != nil { 1060 log.C(ctx).Errorf("error while deleting webhook with ID %s", webhook.ID) 1061 return errors.Wrapf(err, "while deleting webhook with ID %s", webhook.ID) 1062 } 1063 } 1064 1065 return nil 1066 } 1067 1068 func (s *Service) createWebhooks(ctx context.Context, webhooks []*model.WebhookInput, appID string) error { 1069 for _, webhook := range webhooks { 1070 log.C(ctx).Infof("Creating webhook with type %s for application %s", webhook.Type, appID) 1071 if _, err := s.webhookSvc.Create(ctx, appID, *webhook, model.ApplicationWebhookReference); err != nil { 1072 log.C(ctx).Errorf("error while creating webhook for app %s with type %s", appID, webhook.Type) 1073 return errors.Wrapf(err, "error while creating webhook for app %s with type %s", appID, webhook.Type) 1074 } 1075 } 1076 1077 return nil 1078 } 1079 1080 func (s *Service) getTenantMappingData(credentialExchangeStrategyJSON gjson.Result, appID string) (CredentialExchangeStrategyTenantMapping, error) { 1081 tenantMappingType := credentialExchangeStrategyJSON.Get(customTypeProperty).String() 1082 tenantMappingData, ok := s.config.credentialExchangeStrategyTenantMappings[tenantMappingType] 1083 if !ok { 1084 return CredentialExchangeStrategyTenantMapping{}, errors.Errorf("Credential Exchange Strategy has invalid %s value: %s for application with ID %s", customTypeProperty, tenantMappingType, appID) 1085 } 1086 return tenantMappingData, nil 1087 } 1088 1089 func (s *Service) processEnrichedWebhooks(enrichedWebhooks []*graphql.WebhookInput, webhooksFromDB []*model.Webhook) ([]*model.Webhook, []*model.Webhook, []*model.WebhookInput, error) { 1090 tenantMappingRelatedWebhooksFromDB := make([]*model.Webhook, 0) 1091 enrichedWebhookModels := make([]*model.Webhook, 0) 1092 enrichedWebhookModelInputs := make([]*model.WebhookInput, 0) 1093 1094 for _, wh := range enrichedWebhooks { 1095 convertedIn, err := s.webhookConverter.InputFromGraphQL(wh) 1096 if err != nil { 1097 return nil, nil, nil, errors.Wrap(err, "while converting the WebhookInput") 1098 } 1099 1100 enrichedWebhookModelInputs = append(enrichedWebhookModelInputs, convertedIn) 1101 1102 webhookModel := convertedIn.ToWebhook("", "", "") 1103 1104 for _, webhookFromDB := range webhooksFromDB { 1105 if webhookFromDB.Type == convertedIn.Type { 1106 webhookModel.ID = webhookFromDB.ID 1107 webhookModel.ObjectType = webhookFromDB.ObjectType 1108 webhookModel.ObjectID = webhookFromDB.ObjectID 1109 webhookModel.CreatedAt = webhookFromDB.CreatedAt 1110 1111 tenantMappingRelatedWebhooksFromDB = append(tenantMappingRelatedWebhooksFromDB, webhookFromDB) 1112 break 1113 } 1114 } 1115 1116 enrichedWebhookModels = append(enrichedWebhookModels, webhookModel) 1117 } 1118 1119 return tenantMappingRelatedWebhooksFromDB, enrichedWebhookModels, enrichedWebhookModelInputs, nil 1120 } 1121 1122 func (s *Service) processAPIs(ctx context.Context, resourceType directorresource.Type, resourceID string, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, apis []*model.APIDefinitionInput, resourceHashes map[string]uint64) ([]*model.APIDefinition, []*ordFetchRequest, error) { 1123 apisFromDB, err := s.listAPIsInTx(ctx, resourceType, resourceID) 1124 if err != nil { 1125 return nil, nil, err 1126 } 1127 1128 fetchRequests := make([]*ordFetchRequest, 0) 1129 for _, api := range apis { 1130 apiHash := resourceHashes[str.PtrStrToStr(api.OrdID)] 1131 apiFetchRequests, err := s.resyncAPIInTx(ctx, resourceType, resourceID, apisFromDB, bundlesFromDB, packagesFromDB, api, apiHash) 1132 if err != nil { 1133 return nil, nil, err 1134 } 1135 1136 for i := range apiFetchRequests { 1137 fetchRequests = append(fetchRequests, &ordFetchRequest{ 1138 FetchRequest: apiFetchRequests[i], 1139 refObjectOrdID: *api.OrdID, 1140 }) 1141 } 1142 } 1143 1144 apisFromDB, err = s.listAPIsInTx(ctx, resourceType, resourceID) 1145 if err != nil { 1146 return nil, nil, err 1147 } 1148 return apisFromDB, fetchRequests, nil 1149 } 1150 1151 func (s *Service) listAPIsInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.APIDefinition, error) { 1152 tx, err := s.transact.Begin() 1153 if err != nil { 1154 return nil, err 1155 } 1156 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1157 ctx = persistence.SaveToContext(ctx, tx) 1158 1159 var apisFromDB []*model.APIDefinition 1160 1161 if resourceType == directorresource.Application { 1162 apisFromDB, err = s.apiSvc.ListByApplicationID(ctx, resourceID) 1163 } else if resourceType == directorresource.ApplicationTemplateVersion { 1164 apisFromDB, err = s.apiSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1165 } 1166 if err != nil { 1167 return nil, errors.Wrapf(err, "error while listing apis for %s with id %q", resourceType, resourceID) 1168 } 1169 1170 return apisFromDB, tx.Commit() 1171 } 1172 1173 func (s *Service) resyncAPIInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, apisFromDB []*model.APIDefinition, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, api *model.APIDefinitionInput, apiHash uint64) ([]*model.FetchRequest, error) { 1174 tx, err := s.transact.Begin() 1175 if err != nil { 1176 return nil, err 1177 } 1178 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1179 ctx = persistence.SaveToContext(ctx, tx) 1180 1181 fetchRequests, err := s.resyncAPI(ctx, resourceType, resourceID, apisFromDB, bundlesFromDB, packagesFromDB, *api, apiHash) 1182 if err != nil { 1183 return nil, errors.Wrapf(err, "error while resyncing api with ORD ID %q", *api.OrdID) 1184 } 1185 return fetchRequests, tx.Commit() 1186 } 1187 1188 func (s *Service) processEvents(ctx context.Context, resourceType directorresource.Type, resourceID string, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, events []*model.EventDefinitionInput, resourceHashes map[string]uint64) ([]*model.EventDefinition, []*ordFetchRequest, error) { 1189 eventsFromDB, err := s.listEventsInTx(ctx, resourceType, resourceID) 1190 if err != nil { 1191 return nil, nil, err 1192 } 1193 1194 fetchRequests := make([]*ordFetchRequest, 0) 1195 for _, event := range events { 1196 eventHash := resourceHashes[str.PtrStrToStr(event.OrdID)] 1197 eventFetchRequests, err := s.resyncEventInTx(ctx, resourceType, resourceID, eventsFromDB, bundlesFromDB, packagesFromDB, event, eventHash) 1198 if err != nil { 1199 return nil, nil, err 1200 } 1201 1202 for i := range eventFetchRequests { 1203 fetchRequests = append(fetchRequests, &ordFetchRequest{ 1204 FetchRequest: eventFetchRequests[i], 1205 refObjectOrdID: *event.OrdID, 1206 }) 1207 } 1208 } 1209 1210 eventsFromDB, err = s.listEventsInTx(ctx, resourceType, resourceID) 1211 if err != nil { 1212 return nil, nil, err 1213 } 1214 return eventsFromDB, fetchRequests, nil 1215 } 1216 1217 func (s *Service) listEventsInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.EventDefinition, error) { 1218 tx, err := s.transact.Begin() 1219 if err != nil { 1220 return nil, err 1221 } 1222 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1223 ctx = persistence.SaveToContext(ctx, tx) 1224 1225 var eventsFromDB []*model.EventDefinition 1226 if resourceType == directorresource.Application { 1227 eventsFromDB, err = s.eventSvc.ListByApplicationID(ctx, resourceID) 1228 } else if resourceType == directorresource.ApplicationTemplateVersion { 1229 eventsFromDB, err = s.eventSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1230 } 1231 if err != nil { 1232 return nil, errors.Wrapf(err, "error while listing events for %s with id %q", resourceType, resourceID) 1233 } 1234 1235 return eventsFromDB, tx.Commit() 1236 } 1237 1238 func (s *Service) resyncEventInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, eventsFromDB []*model.EventDefinition, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, event *model.EventDefinitionInput, eventHash uint64) ([]*model.FetchRequest, error) { 1239 tx, err := s.transact.Begin() 1240 if err != nil { 1241 return nil, err 1242 } 1243 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1244 ctx = persistence.SaveToContext(ctx, tx) 1245 1246 fetchRequests, err := s.resyncEvent(ctx, resourceType, resourceID, eventsFromDB, bundlesFromDB, packagesFromDB, *event, eventHash) 1247 if err != nil { 1248 return nil, errors.Wrapf(err, "error while resyncing event with ORD ID %q", *event.OrdID) 1249 } 1250 return fetchRequests, tx.Commit() 1251 } 1252 1253 func (s *Service) processTombstones(ctx context.Context, resourceType directorresource.Type, resourceID string, tombstones []*model.TombstoneInput) ([]*model.Tombstone, error) { 1254 tombstonesFromDB, err := s.listTombstonesInTx(ctx, resourceType, resourceID) 1255 if err != nil { 1256 return nil, err 1257 } 1258 1259 for _, tombstone := range tombstones { 1260 if err := s.resyncTombstoneInTx(ctx, resourceType, resourceID, tombstonesFromDB, tombstone); err != nil { 1261 return nil, err 1262 } 1263 } 1264 1265 tombstonesFromDB, err = s.listTombstonesInTx(ctx, resourceType, resourceID) 1266 if err != nil { 1267 return nil, err 1268 } 1269 return tombstonesFromDB, nil 1270 } 1271 1272 func (s *Service) listTombstonesInTx(ctx context.Context, resourceType directorresource.Type, resourceID string) ([]*model.Tombstone, error) { 1273 tx, err := s.transact.Begin() 1274 if err != nil { 1275 return nil, err 1276 } 1277 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1278 ctx = persistence.SaveToContext(ctx, tx) 1279 1280 var tombstonesFromDB []*model.Tombstone 1281 if resourceType == directorresource.Application { 1282 tombstonesFromDB, err = s.tombstoneSvc.ListByApplicationID(ctx, resourceID) 1283 } else if resourceType == directorresource.ApplicationTemplateVersion { 1284 tombstonesFromDB, err = s.tombstoneSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1285 } 1286 if err != nil { 1287 return nil, errors.Wrapf(err, "error while listing tombstones for %s with id %q", resourceType, resourceID) 1288 } 1289 1290 return tombstonesFromDB, tx.Commit() 1291 } 1292 1293 func (s *Service) resyncTombstoneInTx(ctx context.Context, resourceType directorresource.Type, resourceID string, tombstonesFromDB []*model.Tombstone, tombstone *model.TombstoneInput) error { 1294 tx, err := s.transact.Begin() 1295 if err != nil { 1296 return err 1297 } 1298 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1299 ctx = persistence.SaveToContext(ctx, tx) 1300 1301 if err := s.resyncTombstone(ctx, resourceType, resourceID, tombstonesFromDB, *tombstone); err != nil { 1302 return errors.Wrapf(err, "error while resyncing tombstone for resource with ORD ID %q", tombstone.OrdID) 1303 } 1304 return tx.Commit() 1305 } 1306 1307 func (s *Service) resyncPackage(ctx context.Context, resourceType directorresource.Type, resourceID string, packagesFromDB []*model.Package, pkg model.PackageInput, pkgHash uint64) error { 1308 ctx = addFieldToLogger(ctx, "package_ord_id", pkg.OrdID) 1309 if i, found := searchInSlice(len(packagesFromDB), func(i int) bool { 1310 return packagesFromDB[i].OrdID == pkg.OrdID 1311 }); found { 1312 return s.packageSvc.Update(ctx, resourceType, packagesFromDB[i].ID, pkg, pkgHash) 1313 } 1314 1315 _, err := s.packageSvc.Create(ctx, resourceType, resourceID, pkg, pkgHash) 1316 return err 1317 } 1318 1319 func (s *Service) resyncBundle(ctx context.Context, resourceType directorresource.Type, resourceID string, bundlesFromDB []*model.Bundle, bndl model.BundleCreateInput, bndlHash uint64) error { 1320 ctx = addFieldToLogger(ctx, "bundle_ord_id", *bndl.OrdID) 1321 if i, found := searchInSlice(len(bundlesFromDB), func(i int) bool { 1322 return equalStrings(bundlesFromDB[i].OrdID, bndl.OrdID) 1323 }); found { 1324 return s.bundleSvc.UpdateBundle(ctx, resourceType, bundlesFromDB[i].ID, bundleUpdateInputFromCreateInput(bndl), bndlHash) 1325 } 1326 1327 _, err := s.bundleSvc.CreateBundle(ctx, resourceType, resourceID, bndl, bndlHash) 1328 return err 1329 } 1330 1331 func (s *Service) resyncProduct(ctx context.Context, resourceType directorresource.Type, resourceID string, productsFromDB []*model.Product, product model.ProductInput) error { 1332 ctx = addFieldToLogger(ctx, "product_ord_id", product.OrdID) 1333 if i, found := searchInSlice(len(productsFromDB), func(i int) bool { 1334 return productsFromDB[i].OrdID == product.OrdID 1335 }); found { 1336 return s.productSvc.Update(ctx, resourceType, productsFromDB[i].ID, product) 1337 } 1338 1339 _, err := s.productSvc.Create(ctx, resourceType, resourceID, product) 1340 return err 1341 } 1342 1343 func (s *Service) resyncVendor(ctx context.Context, resourceType directorresource.Type, resourceID string, vendorsFromDB []*model.Vendor, vendor model.VendorInput) error { 1344 ctx = addFieldToLogger(ctx, "vendor_ord_id", vendor.OrdID) 1345 if i, found := searchInSlice(len(vendorsFromDB), func(i int) bool { 1346 return vendorsFromDB[i].OrdID == vendor.OrdID 1347 }); found { 1348 return s.vendorSvc.Update(ctx, resourceType, vendorsFromDB[i].ID, vendor) 1349 } 1350 1351 _, err := s.vendorSvc.Create(ctx, resourceType, resourceID, vendor) 1352 return err 1353 } 1354 1355 func (s *Service) resyncAppTemplateVersion(ctx context.Context, appTemplateID string, appTemplateVersionsFromDB []*model.ApplicationTemplateVersion, appTemplateVersion *model.ApplicationTemplateVersionInput) error { 1356 ctx = addFieldToLogger(ctx, "app_template_id", appTemplateID) 1357 if i, found := searchInSlice(len(appTemplateVersionsFromDB), func(i int) bool { 1358 return appTemplateVersionsFromDB[i].Version == appTemplateVersion.Version 1359 }); found { 1360 return s.appTemplateVersionSvc.Update(ctx, appTemplateVersionsFromDB[i].ID, appTemplateID, *appTemplateVersion) 1361 } 1362 1363 _, err := s.appTemplateVersionSvc.Create(ctx, appTemplateID, appTemplateVersion) 1364 return err 1365 } 1366 1367 func (s *Service) resyncAPI(ctx context.Context, resourceType directorresource.Type, resourceID string, apisFromDB []*model.APIDefinition, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, api model.APIDefinitionInput, apiHash uint64) ([]*model.FetchRequest, error) { 1368 ctx = addFieldToLogger(ctx, "api_ord_id", *api.OrdID) 1369 i, isAPIFound := searchInSlice(len(apisFromDB), func(i int) bool { 1370 return equalStrings(apisFromDB[i].OrdID, api.OrdID) 1371 }) 1372 1373 defaultConsumptionBundleID := extractDefaultConsumptionBundle(bundlesFromDB, api.DefaultConsumptionBundle) 1374 defaultTargetURLPerBundle := extractAllBundleReferencesForAPI(bundlesFromDB, api) 1375 1376 var packageID *string 1377 if i, found := searchInSlice(len(packagesFromDB), func(i int) bool { 1378 return equalStrings(&packagesFromDB[i].OrdID, api.OrdPackageID) 1379 }); found { 1380 packageID = &packagesFromDB[i].ID 1381 } 1382 1383 specs := make([]*model.SpecInput, 0, len(api.ResourceDefinitions)) 1384 for _, resourceDef := range api.ResourceDefinitions { 1385 specs = append(specs, resourceDef.ToSpec()) 1386 } 1387 1388 if !isAPIFound { 1389 apiID, err := s.apiSvc.Create(ctx, resourceType, resourceID, nil, packageID, api, nil, defaultTargetURLPerBundle, apiHash, defaultConsumptionBundleID) 1390 if err != nil { 1391 return nil, err 1392 } 1393 1394 fr, err := s.createSpecs(ctx, model.APISpecReference, apiID, specs, resourceType) 1395 if err != nil { 1396 return nil, err 1397 } 1398 1399 return fr, nil 1400 } 1401 1402 allBundleIDsForAPI, err := s.bundleReferenceSvc.GetBundleIDsForObject(ctx, model.BundleAPIReference, &apisFromDB[i].ID) 1403 if err != nil { 1404 return nil, err 1405 } 1406 1407 // in case of API update, we need to filter which ConsumptionBundleReferences should be deleted - those that are stored in db but not present in the input anymore 1408 bundleIDsForDeletion := extractBundleReferencesForDeletion(allBundleIDsForAPI, defaultTargetURLPerBundle) 1409 1410 // in case of API update, we need to filter which ConsumptionBundleReferences should be created - those that are not present in db but are present in the input 1411 defaultTargetURLPerBundleForCreation := extractAllBundleReferencesForCreation(defaultTargetURLPerBundle, allBundleIDsForAPI) 1412 1413 if err = s.apiSvc.UpdateInManyBundles(ctx, resourceType, apisFromDB[i].ID, api, nil, defaultTargetURLPerBundle, defaultTargetURLPerBundleForCreation, bundleIDsForDeletion, apiHash, defaultConsumptionBundleID); err != nil { 1414 return nil, err 1415 } 1416 1417 var fetchRequests []*model.FetchRequest 1418 if api.VersionInput.Value != apisFromDB[i].Version.Value { 1419 fetchRequests, err = s.resyncSpecs(ctx, model.APISpecReference, apisFromDB[i].ID, specs, resourceType) 1420 if err != nil { 1421 return nil, err 1422 } 1423 } else { 1424 fetchRequests, err = s.refetchFailedSpecs(ctx, resourceType, model.APISpecReference, apisFromDB[i].ID) 1425 if err != nil { 1426 return nil, err 1427 } 1428 } 1429 return fetchRequests, nil 1430 } 1431 1432 func (s *Service) resyncEvent(ctx context.Context, resourceType directorresource.Type, resourceID string, eventsFromDB []*model.EventDefinition, bundlesFromDB []*model.Bundle, packagesFromDB []*model.Package, event model.EventDefinitionInput, eventHash uint64) ([]*model.FetchRequest, error) { 1433 ctx = addFieldToLogger(ctx, "event_ord_id", *event.OrdID) 1434 i, isEventFound := searchInSlice(len(eventsFromDB), func(i int) bool { 1435 return equalStrings(eventsFromDB[i].OrdID, event.OrdID) 1436 }) 1437 1438 defaultConsumptionBundleID := extractDefaultConsumptionBundle(bundlesFromDB, event.DefaultConsumptionBundle) 1439 1440 bundleIDsFromBundleReference := make([]string, 0) 1441 for _, br := range event.PartOfConsumptionBundles { 1442 for _, bndl := range bundlesFromDB { 1443 if equalStrings(bndl.OrdID, &br.BundleOrdID) { 1444 bundleIDsFromBundleReference = append(bundleIDsFromBundleReference, bndl.ID) 1445 } 1446 } 1447 } 1448 1449 var packageID *string 1450 if i, found := searchInSlice(len(packagesFromDB), func(i int) bool { 1451 return equalStrings(&packagesFromDB[i].OrdID, event.OrdPackageID) 1452 }); found { 1453 packageID = &packagesFromDB[i].ID 1454 } 1455 1456 specs := make([]*model.SpecInput, 0, len(event.ResourceDefinitions)) 1457 for _, resourceDef := range event.ResourceDefinitions { 1458 specs = append(specs, resourceDef.ToSpec()) 1459 } 1460 1461 if !isEventFound { 1462 eventID, err := s.eventSvc.Create(ctx, resourceType, resourceID, nil, packageID, event, nil, bundleIDsFromBundleReference, eventHash, defaultConsumptionBundleID) 1463 if err != nil { 1464 return nil, err 1465 } 1466 return s.createSpecs(ctx, model.EventSpecReference, eventID, specs, resourceType) 1467 } 1468 1469 allBundleIDsForEvent, err := s.bundleReferenceSvc.GetBundleIDsForObject(ctx, model.BundleEventReference, &eventsFromDB[i].ID) 1470 if err != nil { 1471 return nil, err 1472 } 1473 1474 // in case of Event update, we need to filter which ConsumptionBundleReferences(bundle IDs) should be deleted - those that are stored in db but not present in the input anymore 1475 bundleIDsForDeletion := make([]string, 0) 1476 for _, id := range allBundleIDsForEvent { 1477 if _, found := searchInSlice(len(bundleIDsFromBundleReference), func(i int) bool { 1478 return equalStrings(&bundleIDsFromBundleReference[i], &id) 1479 }); !found { 1480 bundleIDsForDeletion = append(bundleIDsForDeletion, id) 1481 } 1482 } 1483 1484 // in case of Event update, we need to filter which ConsumptionBundleReferences should be created - those that are not present in db but are present in the input 1485 bundleIDsForCreation := make([]string, 0) 1486 for _, id := range bundleIDsFromBundleReference { 1487 if _, found := searchInSlice(len(allBundleIDsForEvent), func(i int) bool { 1488 return equalStrings(&allBundleIDsForEvent[i], &id) 1489 }); !found { 1490 bundleIDsForCreation = append(bundleIDsForCreation, id) 1491 } 1492 } 1493 1494 if err = s.eventSvc.UpdateInManyBundles(ctx, resourceType, eventsFromDB[i].ID, event, nil, bundleIDsFromBundleReference, bundleIDsForCreation, bundleIDsForDeletion, eventHash, defaultConsumptionBundleID); err != nil { 1495 return nil, err 1496 } 1497 1498 var fetchRequests []*model.FetchRequest 1499 if event.VersionInput.Value != eventsFromDB[i].Version.Value { 1500 fetchRequests, err = s.resyncSpecs(ctx, model.EventSpecReference, eventsFromDB[i].ID, specs, resourceType) 1501 if err != nil { 1502 return nil, err 1503 } 1504 } else { 1505 fetchRequests, err = s.refetchFailedSpecs(ctx, resourceType, model.EventSpecReference, eventsFromDB[i].ID) 1506 if err != nil { 1507 return nil, err 1508 } 1509 } 1510 1511 return fetchRequests, nil 1512 } 1513 1514 func (s *Service) createSpecs(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, specs []*model.SpecInput, resourceType directorresource.Type) ([]*model.FetchRequest, error) { 1515 fetchRequests := make([]*model.FetchRequest, 0) 1516 for _, spec := range specs { 1517 if spec == nil { 1518 continue 1519 } 1520 1521 _, fr, err := s.specSvc.CreateByReferenceObjectIDWithDelayedFetchRequest(ctx, *spec, resourceType, objectType, objectID) 1522 if err != nil { 1523 return nil, err 1524 } 1525 fetchRequests = append(fetchRequests, fr) 1526 } 1527 return fetchRequests, nil 1528 } 1529 1530 func (s *Service) resyncSpecs(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, specs []*model.SpecInput, resourceType directorresource.Type) ([]*model.FetchRequest, error) { 1531 if err := s.specSvc.DeleteByReferenceObjectID(ctx, resourceType, objectType, objectID); err != nil { 1532 return nil, err 1533 } 1534 return s.createSpecs(ctx, objectType, objectID, specs, resourceType) 1535 } 1536 1537 func (s *Service) refetchFailedSpecs(ctx context.Context, resourceType directorresource.Type, objectType model.SpecReferenceObjectType, objectID string) ([]*model.FetchRequest, error) { 1538 specIDsFromDB, err := s.specSvc.ListIDByReferenceObjectID(ctx, resourceType, objectType, objectID) 1539 if err != nil { 1540 return nil, err 1541 } 1542 1543 var ( 1544 fetchRequestsFromDB []*model.FetchRequest 1545 tnt string 1546 ) 1547 if resourceType.IsTenantIgnorable() { 1548 fetchRequestsFromDB, err = s.specSvc.ListFetchRequestsByReferenceObjectIDsGlobal(ctx, specIDsFromDB, objectType) 1549 } else { 1550 tnt, err = tenant.LoadFromContext(ctx) 1551 if err != nil { 1552 return nil, err 1553 } 1554 1555 fetchRequestsFromDB, err = s.specSvc.ListFetchRequestsByReferenceObjectIDs(ctx, tnt, specIDsFromDB, objectType) 1556 } 1557 if err != nil { 1558 return nil, err 1559 } 1560 1561 fetchRequests := make([]*model.FetchRequest, 0) 1562 for _, fr := range fetchRequestsFromDB { 1563 if fr.Status != nil && fr.Status.Condition != model.FetchRequestStatusConditionSucceeded { 1564 fetchRequests = append(fetchRequests, fr) 1565 } 1566 } 1567 return fetchRequests, nil 1568 } 1569 1570 func (s *Service) resyncTombstone(ctx context.Context, resourceType directorresource.Type, resourceID string, tombstonesFromDB []*model.Tombstone, tombstone model.TombstoneInput) error { 1571 if i, found := searchInSlice(len(tombstonesFromDB), func(i int) bool { 1572 return tombstonesFromDB[i].OrdID == tombstone.OrdID 1573 }); found { 1574 return s.tombstoneSvc.Update(ctx, resourceType, tombstonesFromDB[i].ID, tombstone) 1575 } 1576 1577 _, err := s.tombstoneSvc.Create(ctx, resourceType, resourceID, tombstone) 1578 return err 1579 } 1580 1581 func (s *Service) fetchAPIDefFromDB(ctx context.Context, resourceType directorresource.Type, resourceID string) (map[string]*model.APIDefinition, error) { 1582 var ( 1583 apisFromDB []*model.APIDefinition 1584 err error 1585 ) 1586 1587 if resourceType == directorresource.ApplicationTemplateVersion { 1588 apisFromDB, err = s.apiSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1589 } else { 1590 apisFromDB, err = s.apiSvc.ListByApplicationID(ctx, resourceID) 1591 } 1592 if err != nil { 1593 return nil, err 1594 } 1595 1596 apiDataFromDB := make(map[string]*model.APIDefinition, len(apisFromDB)) 1597 1598 for _, api := range apisFromDB { 1599 apiOrdID := str.PtrStrToStr(api.OrdID) 1600 apiDataFromDB[apiOrdID] = api 1601 } 1602 1603 return apiDataFromDB, nil 1604 } 1605 1606 func (s *Service) fetchPackagesFromDB(ctx context.Context, resourceType directorresource.Type, resourceID string) (map[string]*model.Package, error) { 1607 var ( 1608 packagesFromDB []*model.Package 1609 err error 1610 ) 1611 1612 if resourceType == directorresource.ApplicationTemplateVersion { 1613 packagesFromDB, err = s.packageSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1614 } else { 1615 packagesFromDB, err = s.packageSvc.ListByApplicationID(ctx, resourceID) 1616 } 1617 if err != nil { 1618 return nil, err 1619 } 1620 1621 packageDataFromDB := make(map[string]*model.Package) 1622 1623 for _, pkg := range packagesFromDB { 1624 packageDataFromDB[pkg.OrdID] = pkg 1625 } 1626 1627 return packageDataFromDB, nil 1628 } 1629 1630 func (s *Service) fetchEventDefFromDB(ctx context.Context, resourceType directorresource.Type, resourceID string) (map[string]*model.EventDefinition, error) { 1631 var ( 1632 eventsFromDB []*model.EventDefinition 1633 err error 1634 ) 1635 1636 if resourceType == directorresource.ApplicationTemplateVersion { 1637 eventsFromDB, err = s.eventSvc.ListByApplicationTemplateVersionID(ctx, resourceID) 1638 } else { 1639 eventsFromDB, err = s.eventSvc.ListByApplicationID(ctx, resourceID) 1640 } 1641 if err != nil { 1642 return nil, err 1643 } 1644 1645 eventDataFromDB := make(map[string]*model.EventDefinition) 1646 1647 for _, event := range eventsFromDB { 1648 eventOrdID := str.PtrStrToStr(event.OrdID) 1649 eventDataFromDB[eventOrdID] = event 1650 } 1651 1652 return eventDataFromDB, nil 1653 } 1654 1655 func (s *Service) fetchBundlesFromDB(ctx context.Context, resourceType directorresource.Type, resourceID string) (map[string]*model.Bundle, error) { 1656 var ( 1657 bundlesFromDB []*model.Bundle 1658 err error 1659 ) 1660 1661 if resourceType == directorresource.ApplicationTemplateVersion { 1662 bundlesFromDB, err = s.bundleSvc.ListByApplicationTemplateVersionIDNoPaging(ctx, resourceID) 1663 } else { 1664 bundlesFromDB, err = s.bundleSvc.ListByApplicationIDNoPaging(ctx, resourceID) 1665 } 1666 if err != nil { 1667 return nil, err 1668 } 1669 1670 bundleDataFromDB := make(map[string]*model.Bundle) 1671 1672 for _, bndl := range bundlesFromDB { 1673 bndlOrdID := str.PtrStrToStr(bndl.OrdID) 1674 bundleDataFromDB[bndlOrdID] = bndl 1675 } 1676 1677 return bundleDataFromDB, nil 1678 } 1679 1680 func (s *Service) fetchResources(ctx context.Context, resource Resource, documents Documents) (ResourcesFromDB, error) { 1681 resourceIDs := make(map[string]directorresource.Type, 0) 1682 1683 if resource.Type == directorresource.Application { 1684 resourceIDs[resource.ID] = directorresource.Application 1685 } 1686 1687 for _, doc := range documents { 1688 if doc.DescribedSystemVersion != nil { 1689 appTemplateID := resource.ID 1690 if resource.Type == directorresource.Application && resource.ParentID != nil { 1691 appTemplateID = *resource.ParentID 1692 } 1693 1694 appTemplateVersion, err := s.getApplicationTemplateVersionByAppTemplateIDAndVersionInTx(ctx, appTemplateID, doc.DescribedSystemVersion.Version) 1695 if err != nil { 1696 return ResourcesFromDB{}, err 1697 } 1698 resourceIDs[appTemplateVersion.ID] = directorresource.ApplicationTemplateVersion 1699 } 1700 } 1701 1702 tx, err := s.transact.Begin() 1703 if err != nil { 1704 return ResourcesFromDB{}, err 1705 } 1706 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1707 1708 ctx = persistence.SaveToContext(ctx, tx) 1709 1710 apiDataFromDB := make(map[string]*model.APIDefinition) 1711 eventDataFromDB := make(map[string]*model.EventDefinition) 1712 packageDataFromDB := make(map[string]*model.Package) 1713 bundleDataFromDB := make(map[string]*model.Bundle) 1714 1715 for resourceID, resourceType := range resourceIDs { 1716 apiData, err := s.fetchAPIDefFromDB(ctx, resourceType, resourceID) 1717 if err != nil { 1718 return ResourcesFromDB{}, errors.Wrapf(err, "while fetching apis for %s with id %s", resourceType, resourceID) 1719 } 1720 1721 eventData, err := s.fetchEventDefFromDB(ctx, resourceType, resourceID) 1722 if err != nil { 1723 return ResourcesFromDB{}, errors.Wrapf(err, "while fetching events for %s with id %s", resourceType, resourceID) 1724 } 1725 1726 packageData, err := s.fetchPackagesFromDB(ctx, resourceType, resourceID) 1727 if err != nil { 1728 return ResourcesFromDB{}, errors.Wrapf(err, "while fetching packages for %s with id %s", resourceType, resourceID) 1729 } 1730 1731 bundleData, err := s.fetchBundlesFromDB(ctx, resourceType, resourceID) 1732 if err != nil { 1733 return ResourcesFromDB{}, errors.Wrapf(err, "while fetching bundles for %s with id %s", resourceType, resourceID) 1734 } 1735 1736 if err = mergo.Merge(&apiDataFromDB, apiData); err != nil { 1737 return ResourcesFromDB{}, err 1738 } 1739 if err = mergo.Merge(&eventDataFromDB, eventData); err != nil { 1740 return ResourcesFromDB{}, err 1741 } 1742 if err = mergo.Merge(&packageDataFromDB, packageData); err != nil { 1743 return ResourcesFromDB{}, err 1744 } 1745 if err = mergo.Merge(&bundleDataFromDB, bundleData); err != nil { 1746 return ResourcesFromDB{}, err 1747 } 1748 } 1749 1750 return ResourcesFromDB{ 1751 APIs: apiDataFromDB, 1752 Events: eventDataFromDB, 1753 Packages: packageDataFromDB, 1754 Bundles: bundleDataFromDB, 1755 }, tx.Commit() 1756 } 1757 1758 func (s *Service) processWebhookAndDocuments(ctx context.Context, cfg MetricsConfig, webhook *model.Webhook, resource Resource, globalResourcesOrdIDs map[string]bool) error { 1759 var ( 1760 documents Documents 1761 baseURL string 1762 err error 1763 ) 1764 1765 metricsCfg := metrics.PusherConfig{ 1766 Enabled: len(cfg.PushEndpoint) > 0, 1767 Endpoint: cfg.PushEndpoint, 1768 MetricName: strings.ReplaceAll(strings.ToLower(cfg.JobName), "-", "_") + "_job_sync_failure_number", 1769 Timeout: cfg.ClientTimeout, 1770 Subsystem: metrics.OrdAggregatorSubsystem, 1771 Labels: []string{metrics.ErrorMetricLabel, metrics.ResourceIDMetricLabel, metrics.ResourceTypeMetricLabel, metrics.CorrelationIDMetricLabel}, 1772 } 1773 1774 ctx = addFieldToLogger(ctx, "resource_id", resource.ID) 1775 ctx = addFieldToLogger(ctx, "resource_type", string(resource.Type)) 1776 1777 if webhook.Type == model.WebhookTypeOpenResourceDiscovery && webhook.URL != nil { 1778 documents, baseURL, err = s.ordClient.FetchOpenResourceDiscoveryDocuments(ctx, resource, webhook) 1779 if err != nil { 1780 metricsPusher := metrics.NewAggregationFailurePusher(metricsCfg) 1781 metricsPusher.ReportAggregationFailureORD(ctx, err.Error()) 1782 1783 return errors.Wrapf(err, "error fetching ORD document for webhook with id %q: %v", webhook.ID, err) 1784 } 1785 } 1786 1787 if len(documents) > 0 { 1788 log.C(ctx).Info("Processing ORD documents") 1789 var validationErrors error 1790 1791 err = s.processDocuments(ctx, resource, baseURL, documents, globalResourcesOrdIDs, &validationErrors) 1792 if err != nil { 1793 metricsPusher := metrics.NewAggregationFailurePusher(metricsCfg) 1794 metricsPusher.ReportAggregationFailureORD(ctx, err.Error()) 1795 1796 log.C(ctx).WithError(err).Errorf("error processing ORD documents: %v", err) 1797 return errors.Wrapf(err, "error processing ORD documents") 1798 } 1799 if ordValidationError, ok := (validationErrors).(*ORDDocumentValidationError); ok { 1800 validationErrors := strings.Split(ordValidationError.Error(), MultiErrorSeparator) 1801 1802 // the first item in the slice is the message 'invalid documents' for the wrapped errors 1803 validationErrors = validationErrors[1:] 1804 1805 metricsPusher := metrics.NewAggregationFailurePusher(metricsCfg) 1806 1807 for i := range validationErrors { 1808 validationErrors[i] = strings.TrimSpace(validationErrors[i]) 1809 metricsPusher.ReportAggregationFailureORD(ctx, validationErrors[i]) 1810 } 1811 1812 log.C(ctx).WithError(ordValidationError.Err).WithField("validation_errors", validationErrors).Error("error processing ORD documents") 1813 return errors.Wrapf(ordValidationError.Err, "error processing ORD documents") 1814 } 1815 log.C(ctx).Info("Successfully processed ORD documents") 1816 } 1817 return nil 1818 } 1819 1820 func (s *Service) getWebhooksWithOrdType(ctx context.Context) ([]*model.Webhook, error) { 1821 tx, err := s.transact.Begin() 1822 if err != nil { 1823 return nil, err 1824 } 1825 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1826 1827 ctx = persistence.SaveToContext(ctx, tx) 1828 ordWebhooks, err := s.webhookSvc.ListByWebhookType(ctx, model.WebhookTypeOpenResourceDiscovery) 1829 if err != nil { 1830 log.C(ctx).WithError(err).Errorf("error while fetching webhooks with type %s", model.WebhookTypeOpenResourceDiscovery) 1831 return nil, err 1832 } 1833 1834 if err := tx.Commit(); err != nil { 1835 return nil, err 1836 } 1837 1838 return ordWebhooks, nil 1839 } 1840 1841 func (s *Service) getApplicationsForAppTemplate(ctx context.Context, appTemplateID string) ([]*model.Application, error) { 1842 tx, err := s.transact.Begin() 1843 if err != nil { 1844 return nil, err 1845 } 1846 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1847 1848 ctx = persistence.SaveToContext(ctx, tx) 1849 apps, err := s.appSvc.ListAllByApplicationTemplateID(ctx, appTemplateID) 1850 if err != nil { 1851 return nil, err 1852 } 1853 1854 if err := tx.Commit(); err != nil { 1855 return nil, err 1856 } 1857 1858 return apps, err 1859 } 1860 1861 func (s *Service) getUniqueLocalTenantID(documents Documents) string { 1862 var uniqueLocalTenantIds []string 1863 localTenants := make(map[string]bool, 0) 1864 var systemInstanceLocalTenantID *string 1865 1866 for _, doc := range documents { 1867 if doc != nil && doc.DescribedSystemInstance != nil { 1868 systemInstanceLocalTenantID = doc.DescribedSystemInstance.LocalTenantID 1869 if systemInstanceLocalTenantID != nil { 1870 if _, exists := localTenants[*systemInstanceLocalTenantID]; !exists { 1871 localTenants[*systemInstanceLocalTenantID] = true 1872 uniqueLocalTenantIds = append(uniqueLocalTenantIds, *doc.DescribedSystemInstance.LocalTenantID) 1873 } 1874 } 1875 } 1876 } 1877 if len(uniqueLocalTenantIds) == 1 { 1878 return uniqueLocalTenantIds[0] 1879 } 1880 1881 return "" 1882 } 1883 1884 func (s *Service) saveLowestOwnerForAppToContext(ctx context.Context, appID string) (context.Context, error) { 1885 internalTntID, err := s.tenantSvc.GetLowestOwnerForResource(ctx, directorresource.Application, appID) 1886 if err != nil { 1887 return nil, err 1888 } 1889 1890 tnt, err := s.tenantSvc.GetTenantByID(ctx, internalTntID) 1891 if err != nil { 1892 return nil, err 1893 } 1894 1895 ctx = tenant.SaveToContext(ctx, internalTntID, tnt.ExternalTenant) 1896 1897 return ctx, nil 1898 } 1899 1900 func (s *Service) processApplicationWebhook(ctx context.Context, cfg MetricsConfig, webhook *model.Webhook, appID string, globalResourcesOrdIDs map[string]bool) error { 1901 tx, err := s.transact.Begin() 1902 if err != nil { 1903 return err 1904 } 1905 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1906 1907 ctx = persistence.SaveToContext(ctx, tx) 1908 1909 ctx, err = s.saveLowestOwnerForAppToContext(ctx, appID) 1910 if err != nil { 1911 return err 1912 } 1913 app, err := s.appSvc.Get(ctx, appID) 1914 if err != nil { 1915 return errors.Wrapf(err, "error while retrieving app with id %q", appID) 1916 } 1917 1918 localTenantID := str.PtrStrToStr(app.LocalTenantID) 1919 ctx = tenant.SaveLocalTenantIDToContext(ctx, localTenantID) 1920 1921 if err = tx.Commit(); err != nil { 1922 return err 1923 } 1924 1925 resource := Resource{ 1926 Type: directorresource.Application, 1927 ID: app.ID, 1928 ParentID: app.ApplicationTemplateID, 1929 Name: app.Name, 1930 LocalTenantID: app.LocalTenantID, 1931 } 1932 if err = s.processWebhookAndDocuments(ctx, cfg, webhook, resource, globalResourcesOrdIDs); err != nil { 1933 return err 1934 } 1935 1936 return nil 1937 } 1938 1939 func (s *Service) processApplicationTemplateWebhook(ctx context.Context, cfg MetricsConfig, webhook *model.Webhook, appTemplateID string, globalResourcesOrdIDs map[string]bool) error { 1940 tx, err := s.transact.Begin() 1941 if err != nil { 1942 return err 1943 } 1944 defer s.transact.RollbackUnlessCommitted(ctx, tx) 1945 1946 ctx = persistence.SaveToContext(ctx, tx) 1947 1948 appTemplate, err := s.appTemplateSvc.Get(ctx, appTemplateID) 1949 if err != nil { 1950 return errors.Wrapf(err, "error while retrieving app template with id %q", appTemplateID) 1951 } 1952 1953 if err = tx.Commit(); err != nil { 1954 return err 1955 } 1956 1957 resource := Resource{ 1958 Type: directorresource.ApplicationTemplate, 1959 ID: appTemplate.ID, 1960 Name: appTemplate.Name, 1961 } 1962 if err = s.processWebhookAndDocuments(ctx, cfg, webhook, resource, globalResourcesOrdIDs); err != nil { 1963 return err 1964 } 1965 1966 return nil 1967 } 1968 1969 func excludeUnnecessaryFetchRequests(fetchRequests []*ordFetchRequest, frIdxToExclude []int) []*ordFetchRequest { 1970 finalFetchRequests := make([]*ordFetchRequest, 0) 1971 for i := range fetchRequests { 1972 shouldExclude := false 1973 for _, idxToExclude := range frIdxToExclude { 1974 if i == idxToExclude { 1975 shouldExclude = true 1976 break 1977 } 1978 } 1979 1980 if !shouldExclude { 1981 finalFetchRequests = append(finalFetchRequests, fetchRequests[i]) 1982 } 1983 } 1984 1985 return finalFetchRequests 1986 } 1987 1988 func hashResources(docs Documents) (map[string]uint64, error) { 1989 resourceHashes := make(map[string]uint64) 1990 1991 for _, doc := range docs { 1992 for _, apiInput := range doc.APIResources { 1993 normalizedAPIDef, err := normalizeAPIDefinition(apiInput) 1994 if err != nil { 1995 return nil, err 1996 } 1997 1998 hash, err := HashObject(normalizedAPIDef) 1999 if err != nil { 2000 return nil, errors.Wrapf(err, "while hashing api definition with ORD ID: %s", str.PtrStrToStr(normalizedAPIDef.OrdID)) 2001 } 2002 2003 resourceHashes[str.PtrStrToStr(apiInput.OrdID)] = hash 2004 } 2005 2006 for _, eventInput := range doc.EventResources { 2007 normalizedEventDef, err := normalizeEventDefinition(eventInput) 2008 if err != nil { 2009 return nil, err 2010 } 2011 2012 hash, err := HashObject(normalizedEventDef) 2013 if err != nil { 2014 return nil, errors.Wrapf(err, "while hashing event definition with ORD ID: %s", str.PtrStrToStr(normalizedEventDef.OrdID)) 2015 } 2016 2017 resourceHashes[str.PtrStrToStr(eventInput.OrdID)] = hash 2018 } 2019 2020 for _, packageInput := range doc.Packages { 2021 normalizedPkg, err := normalizePackage(packageInput) 2022 if err != nil { 2023 return nil, err 2024 } 2025 2026 hash, err := HashObject(normalizedPkg) 2027 if err != nil { 2028 return nil, errors.Wrapf(err, "while hashing package with ORD ID: %s", normalizedPkg.OrdID) 2029 } 2030 2031 resourceHashes[packageInput.OrdID] = hash 2032 } 2033 2034 for _, bundleInput := range doc.ConsumptionBundles { 2035 normalizedBndl, err := normalizeBundle(bundleInput) 2036 if err != nil { 2037 return nil, err 2038 } 2039 2040 hash, err := HashObject(normalizedBndl) 2041 if err != nil { 2042 return nil, errors.Wrapf(err, "while hashing bundle with ORD ID: %v", normalizedBndl.OrdID) 2043 } 2044 2045 resourceHashes[str.PtrStrToStr(bundleInput.OrdID)] = hash 2046 } 2047 } 2048 2049 return resourceHashes, nil 2050 } 2051 2052 func bundleUpdateInputFromCreateInput(in model.BundleCreateInput) model.BundleUpdateInput { 2053 return model.BundleUpdateInput{ 2054 Name: in.Name, 2055 Description: in.Description, 2056 InstanceAuthRequestInputSchema: in.InstanceAuthRequestInputSchema, 2057 DefaultInstanceAuth: in.DefaultInstanceAuth, 2058 OrdID: in.OrdID, 2059 ShortDescription: in.ShortDescription, 2060 Links: in.Links, 2061 Labels: in.Labels, 2062 DocumentationLabels: in.DocumentationLabels, 2063 CredentialExchangeStrategies: in.CredentialExchangeStrategies, 2064 CorrelationIDs: in.CorrelationIDs, 2065 } 2066 } 2067 2068 // extractDefaultConsumptionBundle converts the defaultConsumptionBundle which is bundle ORD_ID into internal bundle_id 2069 func extractDefaultConsumptionBundle(bundlesFromDB []*model.Bundle, defaultConsumptionBundle *string) string { 2070 var bundleID string 2071 if defaultConsumptionBundle == nil { 2072 return bundleID 2073 } 2074 2075 for _, bndl := range bundlesFromDB { 2076 if equalStrings(bndl.OrdID, defaultConsumptionBundle) { 2077 bundleID = bndl.ID 2078 break 2079 } 2080 } 2081 return bundleID 2082 } 2083 2084 func extractAllBundleReferencesForAPI(bundlesFromDB []*model.Bundle, api model.APIDefinitionInput) map[string]string { 2085 defaultTargetURLPerBundle := make(map[string]string) 2086 lenTargetURLs := len(gjson.ParseBytes(api.TargetURLs).Array()) 2087 for _, br := range api.PartOfConsumptionBundles { 2088 for _, bndl := range bundlesFromDB { 2089 if equalStrings(bndl.OrdID, &br.BundleOrdID) { 2090 if br.DefaultTargetURL == "" && lenTargetURLs == 1 { 2091 defaultTargetURLPerBundle[bndl.ID] = gjson.ParseBytes(api.TargetURLs).Array()[0].String() 2092 } else { 2093 defaultTargetURLPerBundle[bndl.ID] = br.DefaultTargetURL 2094 } 2095 } 2096 } 2097 } 2098 return defaultTargetURLPerBundle 2099 } 2100 2101 func extractAllBundleReferencesForCreation(defaultTargetURLPerBundle map[string]string, allBundleIDsForAPI []string) map[string]string { 2102 defaultTargetURLPerBundleForCreation := make(map[string]string) 2103 for bndlID, defaultEntryPoint := range defaultTargetURLPerBundle { 2104 if _, found := searchInSlice(len(allBundleIDsForAPI), func(i int) bool { 2105 return equalStrings(&allBundleIDsForAPI[i], &bndlID) 2106 }); !found { 2107 defaultTargetURLPerBundleForCreation[bndlID] = defaultEntryPoint 2108 delete(defaultTargetURLPerBundle, bndlID) 2109 } 2110 } 2111 return defaultTargetURLPerBundleForCreation 2112 } 2113 2114 func extractBundleReferencesForDeletion(allBundleIDsForAPI []string, defaultTargetURLPerBundle map[string]string) []string { 2115 bundleIDsToBeDeleted := make([]string, 0) 2116 2117 for _, bndlID := range allBundleIDsForAPI { 2118 if _, ok := defaultTargetURLPerBundle[bndlID]; !ok { 2119 bundleIDsToBeDeleted = append(bundleIDsToBeDeleted, bndlID) 2120 } 2121 } 2122 2123 return bundleIDsToBeDeleted 2124 } 2125 2126 func equalStrings(first, second *string) bool { 2127 return first != nil && second != nil && *first == *second 2128 } 2129 2130 func searchInSlice(length int, f func(i int) bool) (int, bool) { 2131 for i := 0; i < length; i++ { 2132 if f(i) { 2133 return i, true 2134 } 2135 } 2136 return -1, false 2137 } 2138 2139 func addFieldToLogger(ctx context.Context, fieldName, fieldValue string) context.Context { 2140 logger := log.LoggerFromContext(ctx) 2141 logger = logger.WithField(fieldName, fieldValue) 2142 return log.ContextWithLogger(ctx, logger) 2143 } 2144 2145 func createWebhookInput(credentialExchangeStrategyJSON gjson.Result, tenantMappingData CredentialExchangeStrategyTenantMapping) *graphql.WebhookInput { 2146 inputMode := graphql.WebhookMode(tenantMappingData.Mode) 2147 return &graphql.WebhookInput{ 2148 URL: str.Ptr(credentialExchangeStrategyJSON.Get(callbackURLProperty).String()), 2149 Auth: &graphql.AuthInput{ 2150 AccessStrategy: str.Ptr(string(accessstrategy.CMPmTLSAccessStrategy)), 2151 }, 2152 Mode: &inputMode, 2153 Version: str.Ptr(tenantMappingData.Version), 2154 } 2155 } 2156 2157 func isWebhookDataEqual(tenantMappingRelatedWebhooksFromDB, enrichedWhModels []*model.Webhook) (bool, error) { 2158 appWhsFromDBMarshaled, err := json.Marshal(tenantMappingRelatedWebhooksFromDB) 2159 if err != nil { 2160 return false, errors.Wrapf(err, "while marshalling webhooks from DB") 2161 } 2162 2163 appWhsFromDBHash, err := HashObject(string(appWhsFromDBMarshaled)) 2164 if err != nil { 2165 return false, errors.Wrapf(err, "while hashing webhooks from DB") 2166 } 2167 2168 enrichedWhsMarshaled, err := json.Marshal(enrichedWhModels) 2169 if err != nil { 2170 return false, errors.Wrapf(err, "while marshalling webhooks from DB") 2171 } 2172 2173 enrichedHash, err := HashObject(string(enrichedWhsMarshaled)) 2174 if err != nil { 2175 return false, errors.Wrapf(err, "while hashing webhooks from ORD document") 2176 } 2177 2178 if strconv.FormatUint(appWhsFromDBHash, 10) == strconv.FormatUint(enrichedHash, 10) { 2179 return true, nil 2180 } 2181 2182 return false, nil 2183 }