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  }