github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/resync/event_page.go (about) 1 package resync 2 3 import ( 4 "context" 5 "encoding/json" 6 "regexp" 7 8 "github.com/kyma-incubator/compass/components/director/internal/model" 9 "github.com/kyma-incubator/compass/components/director/pkg/log" 10 "github.com/kyma-incubator/compass/components/director/pkg/tenant" 11 "github.com/pkg/errors" 12 "github.com/tidwall/gjson" 13 ) 14 15 // GlobalAccountRegex determines whether event entity type is global account 16 const GlobalAccountRegex = "^GLOBALACCOUNT_.*|GlobalAccount" 17 18 // EventsPage represents a page returned by the Events API - its payload, 19 // along with the mappings required for converting the page to a set of tenants 20 type EventsPage struct { 21 FieldMapping TenantFieldMapping 22 MovedSubaccountsFieldMapping MovedSubaccountsFieldMapping 23 ProviderName string 24 Payload []byte 25 } 26 27 // GetMovedSubaccounts parses the data from the page payload to MovedSubaccountMappingInput 28 func (ep EventsPage) GetMovedSubaccounts(ctx context.Context) []model.MovedSubaccountMappingInput { 29 eds := ep.getEventsDetails(ctx) 30 mappings := make([]model.MovedSubaccountMappingInput, 0, len(eds)) 31 for _, detail := range eds { 32 mapping, err := ep.eventDataToMovedSubaccount(ctx, detail) 33 if err != nil { 34 log.C(ctx).Warnf("Error: %s. Could not convert tenant: %s", err.Error(), string(detail)) 35 continue 36 } 37 38 mappings = append(mappings, *mapping) 39 } 40 41 return mappings 42 } 43 44 // GetTenantMappings parses the data from the page payload to BusinessTenantMappingInput 45 func (ep EventsPage) GetTenantMappings(ctx context.Context, eventsType EventsType) []model.BusinessTenantMappingInput { 46 eds := ep.getEventsDetails(ctx) 47 tenants := make([]model.BusinessTenantMappingInput, 0, len(eds)) 48 for _, detail := range eds { 49 mapping, err := ep.eventDataToTenant(ctx, eventsType, detail) 50 if err != nil { 51 log.C(ctx).Warnf("Error: %s. Could not convert tenant: %s", err.Error(), string(detail)) 52 continue 53 } 54 55 tenants = append(tenants, *mapping) 56 } 57 58 return tenants 59 } 60 61 func (ep EventsPage) getEventsDetails(ctx context.Context) [][]byte { 62 tenantDetails := make([][]byte, 0) 63 gjson.GetBytes(ep.Payload, ep.FieldMapping.EventsField).ForEach(func(key gjson.Result, event gjson.Result) bool { 64 entityType := event.Get(ep.FieldMapping.EntityTypeField) 65 globalAccountGUID := event.Get(ep.FieldMapping.GlobalAccountGUIDField) 66 details := event.Get(ep.FieldMapping.DetailsField).Map() 67 details[ep.FieldMapping.EntityTypeField] = entityType 68 details[ep.FieldMapping.GlobalAccountKey] = globalAccountGUID 69 allDetails := make(map[string]interface{}) 70 for key, result := range details { 71 switch result.Type { 72 case gjson.String: 73 allDetails[key] = result.String() 74 case gjson.Number: 75 allDetails[key] = result.Float() 76 case gjson.True: 77 allDetails[key] = true 78 case gjson.False: 79 allDetails[key] = false 80 case gjson.Null: 81 allDetails[key] = nil 82 default: 83 log.C(ctx).Debugf("Unknown property type %s", result.Type) 84 } 85 } 86 currentTenantDetails, err := json.Marshal(allDetails) 87 if err != nil { 88 log.C(ctx).Errorf("failed to marshal tenant details: %v", err) 89 return false 90 } 91 tenantDetails = append(tenantDetails, currentTenantDetails) 92 return true 93 }) 94 return tenantDetails 95 } 96 97 func (ep EventsPage) eventDataToMovedSubaccount(ctx context.Context, eventData []byte) (*model.MovedSubaccountMappingInput, error) { 98 jsonPayload := string(eventData) 99 if !gjson.Valid(jsonPayload) { 100 return nil, errors.Errorf("invalid json Payload") 101 } 102 103 id, ok := gjson.GetBytes(eventData, ep.MovedSubaccountsFieldMapping.SubaccountID).Value().(string) 104 if !ok { 105 return nil, errors.Errorf("invalid format of %s field", ep.MovedSubaccountsFieldMapping.SubaccountID) 106 } 107 108 source, ok := gjson.GetBytes(eventData, ep.MovedSubaccountsFieldMapping.SourceTenant).Value().(string) 109 if !ok { 110 return nil, errors.Errorf("invalid format of %s field", ep.MovedSubaccountsFieldMapping.SourceTenant) 111 } 112 113 target, ok := gjson.GetBytes(eventData, ep.MovedSubaccountsFieldMapping.TargetTenant).Value().(string) 114 if !ok { 115 return nil, errors.Errorf("invalid format of %s field", ep.MovedSubaccountsFieldMapping.TargetTenant) 116 } 117 118 nameResult := gjson.Get(jsonPayload, ep.FieldMapping.NameField) 119 if !nameResult.Exists() { 120 return nil, invalidFieldFormatError(ep.FieldMapping.NameField) 121 } 122 123 subdomain := gjson.Get(jsonPayload, ep.FieldMapping.SubdomainField) 124 if !subdomain.Exists() { 125 log.C(ctx).Warnf("Missig or invalid format of field: %s for tenant with ID: %s", ep.FieldMapping.SubdomainField, id) 126 } 127 128 licenseType := gjson.Get(jsonPayload, ep.FieldMapping.LicenseTypeField) 129 var licenseTypeValue *string 130 if licenseType.Exists() && licenseType.Type == gjson.String { 131 licenseTypeValue = &licenseType.Str 132 } else { 133 log.C(ctx).Warnf("Missing or invalid format of licenseType field: %s for tenant with ID: %s", ep.FieldMapping.LicenseTypeField, id) 134 } 135 136 subaccountInput, err := constructSubaccountTenant(ctx, jsonPayload, nameResult.String(), subdomain.String(), id, licenseTypeValue, ep) 137 if err != nil { 138 return nil, err 139 } 140 141 return &model.MovedSubaccountMappingInput{ 142 TenantMappingInput: *subaccountInput, 143 SubaccountID: id, 144 SourceTenant: source, 145 TargetTenant: target, 146 }, nil 147 } 148 149 func (ep EventsPage) eventDataToTenant(ctx context.Context, eventType EventsType, eventData []byte) (*model.BusinessTenantMappingInput, error) { 150 jsonPayload := string(eventData) 151 if !gjson.Valid(jsonPayload) { 152 return nil, errors.Errorf("invalid json Payload") 153 } 154 if eventType == CreatedAccountType && ep.FieldMapping.DiscriminatorField != "" { 155 discriminatorResult := gjson.Get(jsonPayload, ep.FieldMapping.DiscriminatorField) 156 if !discriminatorResult.Exists() { 157 return nil, invalidFieldFormatError(ep.FieldMapping.DiscriminatorField) 158 } 159 if discriminatorResult.String() != ep.FieldMapping.DiscriminatorValue { 160 return nil, nil 161 } 162 } 163 164 id, err := determineTenantID(jsonPayload, ep.FieldMapping) 165 if err != nil { 166 return nil, err 167 } 168 169 nameResult := gjson.Get(jsonPayload, ep.FieldMapping.NameField) 170 if !nameResult.Exists() { 171 log.C(ctx).Warnf("Missing or invalid format of name field: %s for tenant with ID: %s", ep.FieldMapping.NameField, id) 172 } 173 174 subdomain := gjson.Get(jsonPayload, ep.FieldMapping.SubdomainField) 175 if !subdomain.Exists() { 176 log.C(ctx).Warnf("Missing or invalid format of subdomain field: %s for tenant with ID: %s", ep.FieldMapping.SubdomainField, id) 177 } 178 179 entityType := gjson.Get(jsonPayload, ep.FieldMapping.EntityTypeField) 180 if !entityType.Exists() { 181 return nil, invalidFieldFormatError(ep.FieldMapping.EntityTypeField) 182 } 183 184 licenseType := gjson.Get(jsonPayload, ep.FieldMapping.LicenseTypeField) 185 var licenseTypeValue *string 186 if licenseType.Exists() && licenseType.Type == gjson.String { 187 licenseTypeValue = &licenseType.Str 188 } else { 189 log.C(ctx).Warnf("Missing or invalid format of licenseType field: %s for tenant with ID: %s", ep.FieldMapping.LicenseTypeField, id) 190 } 191 192 globalAccountRegex := regexp.MustCompile(GlobalAccountRegex) 193 if globalAccountRegex.MatchString(entityType.String()) { 194 return constructGlobalAccountTenant(ctx, jsonPayload, nameResult.String(), subdomain.String(), id, licenseTypeValue, ep), nil 195 } else { 196 return constructSubaccountTenant(ctx, jsonPayload, nameResult.String(), subdomain.String(), id, licenseTypeValue, ep) 197 } 198 } 199 200 func constructGlobalAccountTenant(ctx context.Context, jsonPayload, name, subdomain, externalTenant string, licenseType *string, ep EventsPage) *model.BusinessTenantMappingInput { 201 parentID := "" 202 customerIDResult := gjson.Get(jsonPayload, ep.FieldMapping.CustomerIDField) 203 if !customerIDResult.Exists() { 204 log.C(ctx).Warnf("Missig or invalid format of field: %s for tenant with id: %s", ep.FieldMapping.CustomerIDField, externalTenant) 205 } else { 206 parentID = customerIDResult.String() 207 } 208 return &model.BusinessTenantMappingInput{ 209 Name: name, 210 ExternalTenant: externalTenant, 211 Parent: parentID, 212 Subdomain: subdomain, 213 Region: "", 214 Type: tenant.TypeToStr(tenant.Account), 215 Provider: ep.ProviderName, 216 LicenseType: licenseType, 217 } 218 } 219 220 func constructSubaccountTenant(ctx context.Context, jsonPayload, name, subdomain, externalTenant string, licenseType *string, ep EventsPage) (*model.BusinessTenantMappingInput, error) { 221 regionField := gjson.Get(jsonPayload, ep.FieldMapping.RegionField) 222 if !regionField.Exists() { 223 log.C(ctx).Debugf("Missing or invalid format of region field: %s for tenant with ID: %s", ep.FieldMapping.RegionField, externalTenant) 224 } 225 region := regionField.String() 226 parentIDField := gjson.Get(jsonPayload, ep.FieldMapping.GlobalAccountKey) 227 if !parentIDField.Exists() { 228 return nil, invalidFieldFormatError(ep.FieldMapping.GlobalAccountKey) 229 } 230 parentID := parentIDField.String() 231 return &model.BusinessTenantMappingInput{ 232 Name: name, 233 ExternalTenant: externalTenant, 234 Parent: parentID, 235 Subdomain: subdomain, 236 Region: region, 237 Type: tenant.TypeToStr(tenant.Subaccount), 238 Provider: ep.ProviderName, 239 LicenseType: licenseType, 240 }, nil 241 } 242 243 // Returns id of the fetched tenant, since there are multiple possible names for the ID field 244 func determineTenantID(jsonPayload string, mapping TenantFieldMapping) (string, error) { 245 if gjson.Get(jsonPayload, mapping.IDField).Exists() { 246 return gjson.Get(jsonPayload, mapping.IDField).String(), nil 247 } else if gjson.Get(jsonPayload, mapping.GlobalAccountGUIDField).Exists() { 248 return gjson.Get(jsonPayload, mapping.GlobalAccountGUIDField).String(), nil 249 } else if gjson.Get(jsonPayload, mapping.SubaccountIDField).Exists() { 250 return gjson.Get(jsonPayload, mapping.SubaccountIDField).String(), nil 251 } 252 return "", errors.Errorf("Missing or invalid format of the ID field") 253 } 254 255 func invalidFieldFormatError(fieldName string) error { 256 return errors.Errorf("invalid format of %s field", fieldName) 257 }