github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/open_resource_discovery/global_registry.go (about)

     1  package ord
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
     7  	directorresource "github.com/kyma-incubator/compass/components/director/pkg/resource"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/internal/model"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // GlobalRegistryService processes global resources (products and vendors) provided via global registry.
    14  //
    15  //go:generate mockery --name=GlobalRegistryService --output=automock --outpkg=automock --case=underscore --disable-version-string
    16  type GlobalRegistryService interface {
    17  	SyncGlobalResources(ctx context.Context) (map[string]bool, error)
    18  	ListGlobalResources(ctx context.Context) (map[string]bool, error)
    19  }
    20  
    21  // GlobalRegistryConfig contains configuration for GlobalRegistryService.
    22  type GlobalRegistryConfig struct {
    23  	URL string `envconfig:"APP_GLOBAL_REGISTRY_URL"`
    24  }
    25  
    26  type globalRegistryService struct {
    27  	config GlobalRegistryConfig
    28  
    29  	transact persistence.Transactioner
    30  
    31  	vendorService  GlobalVendorService
    32  	productService GlobalProductService
    33  
    34  	ordClient Client
    35  
    36  	credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping
    37  }
    38  
    39  // NewGlobalRegistryService creates new instance of GlobalRegistryService.
    40  func NewGlobalRegistryService(transact persistence.Transactioner, config GlobalRegistryConfig, vendorService GlobalVendorService, productService GlobalProductService, ordClient Client, credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping) *globalRegistryService {
    41  	return &globalRegistryService{
    42  		transact:                                 transact,
    43  		config:                                   config,
    44  		vendorService:                            vendorService,
    45  		productService:                           productService,
    46  		ordClient:                                ordClient,
    47  		credentialExchangeStrategyTenantMappings: credentialExchangeStrategyTenantMappings,
    48  	}
    49  }
    50  
    51  // SyncGlobalResources syncs global resources (products and vendors) provided via global registry.
    52  func (s *globalRegistryService) SyncGlobalResources(ctx context.Context) (map[string]bool, error) {
    53  	// dummy app used only for logging
    54  	resource := Resource{
    55  		ID:   "global-registry",
    56  		Name: "global-registry",
    57  		Type: directorresource.Application,
    58  	}
    59  	documents, _, err := s.ordClient.FetchOpenResourceDiscoveryDocuments(ctx, resource, &model.Webhook{
    60  		Type: model.WebhookTypeOpenResourceDiscovery,
    61  		URL:  &s.config.URL,
    62  	})
    63  	if err != nil {
    64  		return nil, errors.Wrapf(err, "while fetching global registry documents from %s", s.config.URL)
    65  	}
    66  
    67  	if err := documents.Validate(s.config.URL, ResourcesFromDB{}, nil, map[string]bool{}, s.credentialExchangeStrategyTenantMappings); err != nil {
    68  		return nil, errors.Wrap(err, "while validating global registry documents")
    69  	}
    70  
    71  	vendorsInput := make([]*model.VendorInput, 0)
    72  	productsInput := make([]*model.ProductInput, 0)
    73  	packagesInput := make([]*model.PackageInput, 0)
    74  	bundlesInput := make([]*model.BundleCreateInput, 0)
    75  	apisInput := make([]*model.APIDefinitionInput, 0)
    76  	eventsInput := make([]*model.EventDefinitionInput, 0)
    77  	tombstonesInput := make([]*model.TombstoneInput, 0)
    78  	for _, doc := range documents {
    79  		vendorsInput = append(vendorsInput, doc.Vendors...)
    80  		productsInput = append(productsInput, doc.Products...)
    81  		packagesInput = append(packagesInput, doc.Packages...)
    82  		bundlesInput = append(bundlesInput, doc.ConsumptionBundles...)
    83  		apisInput = append(apisInput, doc.APIResources...)
    84  		eventsInput = append(eventsInput, doc.EventResources...)
    85  		tombstonesInput = append(tombstonesInput, doc.Tombstones...)
    86  	}
    87  
    88  	if len(packagesInput) > 0 || len(bundlesInput) > 0 || len(apisInput) > 0 || len(eventsInput) > 0 || len(tombstonesInput) > 0 {
    89  		return nil, errors.New("global registry supports only vendors and products")
    90  	}
    91  
    92  	tx, err := s.transact.Begin()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defer s.transact.RollbackUnlessCommitted(ctx, tx)
    97  	ctx = persistence.SaveToContext(ctx, tx)
    98  
    99  	vendorsFromDB, err := s.processVendors(ctx, vendorsInput)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	productsFromDB, err := s.processProducts(ctx, productsInput)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	globalResourceOrdIDs := make(map[string]bool, len(vendorsFromDB)+len(productsFromDB))
   110  	for _, vendor := range vendorsFromDB {
   111  		globalResourceOrdIDs[vendor.OrdID] = true
   112  	}
   113  	for _, product := range productsFromDB {
   114  		globalResourceOrdIDs[product.OrdID] = true
   115  	}
   116  
   117  	return globalResourceOrdIDs, tx.Commit()
   118  }
   119  
   120  func (s *globalRegistryService) ListGlobalResources(ctx context.Context) (map[string]bool, error) {
   121  	tx, err := s.transact.Begin()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	defer s.transact.RollbackUnlessCommitted(ctx, tx)
   126  	ctx = persistence.SaveToContext(ctx, tx)
   127  
   128  	vendorsFromDB, err := s.vendorService.ListGlobal(ctx)
   129  	if err != nil {
   130  		return nil, errors.Wrap(err, "error while listing global vendors")
   131  	}
   132  	productsFromDB, err := s.productService.ListGlobal(ctx)
   133  	if err != nil {
   134  		return nil, errors.Wrap(err, "error while listing global products")
   135  	}
   136  
   137  	globalResourceOrdIDs := make(map[string]bool, len(vendorsFromDB)+len(productsFromDB))
   138  	for _, vendor := range vendorsFromDB {
   139  		globalResourceOrdIDs[vendor.OrdID] = true
   140  	}
   141  	for _, product := range productsFromDB {
   142  		globalResourceOrdIDs[product.OrdID] = true
   143  	}
   144  
   145  	return globalResourceOrdIDs, tx.Commit()
   146  }
   147  
   148  func (s *globalRegistryService) processVendors(ctx context.Context, vendors []*model.VendorInput) ([]*model.Vendor, error) {
   149  	vendorsFromDB, err := s.vendorService.ListGlobal(ctx)
   150  	if err != nil {
   151  		return nil, errors.Wrap(err, "error while listing global vendors")
   152  	}
   153  
   154  	for _, vendor := range vendors {
   155  		if err := s.resyncVendor(ctx, vendorsFromDB, *vendor); err != nil {
   156  			return nil, errors.Wrapf(err, "error while resyncing vendor with ORD ID %q", vendor.OrdID)
   157  		}
   158  	}
   159  
   160  	for _, vendor := range vendorsFromDB {
   161  		if _, found := searchInSlice(len(vendors), func(i int) bool {
   162  			return vendors[i].OrdID == vendor.OrdID
   163  		}); !found {
   164  			if err := s.vendorService.DeleteGlobal(ctx, vendor.ID); err != nil {
   165  				return nil, errors.Wrapf(err, "error while deleting vendor with ID %q", vendor.ID)
   166  			}
   167  		}
   168  	}
   169  
   170  	return s.vendorService.ListGlobal(ctx)
   171  }
   172  
   173  func (s *globalRegistryService) processProducts(ctx context.Context, products []*model.ProductInput) ([]*model.Product, error) {
   174  	productsFromDB, err := s.productService.ListGlobal(ctx)
   175  	if err != nil {
   176  		return nil, errors.Wrap(err, "error while listing global products")
   177  	}
   178  
   179  	for _, product := range products {
   180  		if err := s.resyncProduct(ctx, productsFromDB, *product); err != nil {
   181  			return nil, errors.Wrapf(err, "error while resyncing product with ORD ID %q", product.OrdID)
   182  		}
   183  	}
   184  
   185  	for _, product := range productsFromDB {
   186  		if _, found := searchInSlice(len(products), func(i int) bool {
   187  			return products[i].OrdID == product.OrdID
   188  		}); !found {
   189  			if err := s.productService.DeleteGlobal(ctx, product.ID); err != nil {
   190  				return nil, errors.Wrapf(err, "error while deleting product with ID %q", product.ID)
   191  			}
   192  		}
   193  	}
   194  
   195  	return s.productService.ListGlobal(ctx)
   196  }
   197  
   198  func (s *globalRegistryService) resyncVendor(ctx context.Context, vendorsFromDB []*model.Vendor, vendor model.VendorInput) error {
   199  	ctx = addFieldToLogger(ctx, "vendor_ord_id", vendor.OrdID)
   200  	if i, found := searchInSlice(len(vendorsFromDB), func(i int) bool {
   201  		return vendorsFromDB[i].OrdID == vendor.OrdID
   202  	}); found {
   203  		return s.vendorService.UpdateGlobal(ctx, vendorsFromDB[i].ID, vendor)
   204  	}
   205  	_, err := s.vendorService.CreateGlobal(ctx, vendor)
   206  	return err
   207  }
   208  
   209  func (s *globalRegistryService) resyncProduct(ctx context.Context, productsFromDB []*model.Product, product model.ProductInput) error {
   210  	ctx = addFieldToLogger(ctx, "product_ord_id", product.OrdID)
   211  	if i, found := searchInSlice(len(productsFromDB), func(i int) bool {
   212  		return productsFromDB[i].OrdID == product.OrdID
   213  	}); found {
   214  		return s.productService.UpdateGlobal(ctx, productsFromDB[i].ID, product)
   215  	}
   216  	_, err := s.productService.CreateGlobal(ctx, product)
   217  	return err
   218  }