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 }