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  }