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

     1  package tenant
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/internal/repo"
     7  	"github.com/kyma-incubator/compass/components/director/pkg/str"
     8  	tenantpkg "github.com/kyma-incubator/compass/components/director/pkg/tenant"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/internal/model"
    11  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    12  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const (
    18  	// SubdomainLabelKey is the key of the tenant label for subdomain.
    19  	SubdomainLabelKey = "subdomain"
    20  	// RegionLabelKey is the key of the tenant label for region.
    21  	RegionLabelKey = "region"
    22  	// LicenseTypeLabelKey is the key of the tenant label for licensetype.
    23  	LicenseTypeLabelKey = "licensetype"
    24  )
    25  
    26  // TenantMappingRepository is responsible for the repo-layer tenant operations.
    27  //
    28  //go:generate mockery --name=TenantMappingRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    29  type TenantMappingRepository interface {
    30  	UnsafeCreate(ctx context.Context, item model.BusinessTenantMapping) error
    31  	Upsert(ctx context.Context, item model.BusinessTenantMapping) error
    32  	Update(ctx context.Context, model *model.BusinessTenantMapping) error
    33  	Get(ctx context.Context, id string) (*model.BusinessTenantMapping, error)
    34  	GetByExternalTenant(ctx context.Context, externalTenant string) (*model.BusinessTenantMapping, error)
    35  	Exists(ctx context.Context, id string) (bool, error)
    36  	List(ctx context.Context) ([]*model.BusinessTenantMapping, error)
    37  	ListPageBySearchTerm(ctx context.Context, searchTerm string, pageSize int, cursor string) (*model.BusinessTenantMappingPage, error)
    38  	ExistsByExternalTenant(ctx context.Context, externalTenant string) (bool, error)
    39  	DeleteByExternalTenant(ctx context.Context, externalTenant string) error
    40  	GetLowestOwnerForResource(ctx context.Context, resourceType resource.Type, objectID string) (string, error)
    41  	ListByExternalTenants(ctx context.Context, externalTenant []string) ([]*model.BusinessTenantMapping, error)
    42  	ListByParentAndType(ctx context.Context, parentID string, tenantType tenantpkg.Type) ([]*model.BusinessTenantMapping, error)
    43  	ListByType(ctx context.Context, tenantType tenantpkg.Type) ([]*model.BusinessTenantMapping, error)
    44  	GetCustomerIDParentRecursively(ctx context.Context, tenantID string) (string, error)
    45  }
    46  
    47  // LabelUpsertService is responsible for creating, or updating already existing labels, and their label definitions.
    48  //
    49  //go:generate mockery --name=LabelUpsertService --output=automock --outpkg=automock --case=underscore --disable-version-string
    50  type LabelUpsertService interface {
    51  	UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error
    52  }
    53  
    54  // LabelRepository is responsible for the repo-layer label operations.
    55  //
    56  //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    57  type LabelRepository interface {
    58  	ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error)
    59  }
    60  
    61  // UIDService is responsible for generating GUIDs, which will be used as internal tenant IDs when tenants are created.
    62  //
    63  //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string
    64  type UIDService interface {
    65  	Generate() string
    66  }
    67  
    68  type labeledService struct {
    69  	service
    70  	labelRepo      LabelRepository
    71  	labelUpsertSvc LabelUpsertService
    72  }
    73  
    74  type service struct {
    75  	uidService        UIDService
    76  	tenantMappingRepo TenantMappingRepository
    77  	converter         BusinessTenantMappingConverter
    78  }
    79  
    80  // NewService returns a new object responsible for service-layer tenant operations.
    81  func NewService(tenantMapping TenantMappingRepository, uidService UIDService, converter BusinessTenantMappingConverter) *service {
    82  	return &service{
    83  		uidService:        uidService,
    84  		tenantMappingRepo: tenantMapping,
    85  		converter:         converter,
    86  	}
    87  }
    88  
    89  // NewServiceWithLabels returns a new entity responsible for service-layer tenant operations, including operations with labels like listing all labels related to the given tenant.
    90  func NewServiceWithLabels(tenantMapping TenantMappingRepository, uidService UIDService, labelRepo LabelRepository, labelUpsertSvc LabelUpsertService, converter BusinessTenantMappingConverter) *labeledService {
    91  	return &labeledService{
    92  		service: service{
    93  			uidService:        uidService,
    94  			tenantMappingRepo: tenantMapping,
    95  			converter:         converter,
    96  		},
    97  		labelRepo:      labelRepo,
    98  		labelUpsertSvc: labelUpsertSvc,
    99  	}
   100  }
   101  
   102  // GetExternalTenant returns the external tenant ID of the tenant with the corresponding internal tenant ID.
   103  func (s *service) GetExternalTenant(ctx context.Context, id string) (string, error) {
   104  	mapping, err := s.tenantMappingRepo.Get(ctx, id)
   105  	if err != nil {
   106  		return "", errors.Wrap(err, "while getting the external tenant")
   107  	}
   108  
   109  	return mapping.ExternalTenant, nil
   110  }
   111  
   112  // GetInternalTenant returns the internal tenant ID of the tenant with the corresponding external tenant ID.
   113  func (s *service) GetInternalTenant(ctx context.Context, externalTenant string) (string, error) {
   114  	mapping, err := s.tenantMappingRepo.GetByExternalTenant(ctx, externalTenant)
   115  	if err != nil {
   116  		return "", errors.Wrap(err, "while getting the internal tenant")
   117  	}
   118  
   119  	return mapping.ID, nil
   120  }
   121  
   122  // List returns all tenants present in the Compass storage.
   123  func (s *service) List(ctx context.Context) ([]*model.BusinessTenantMapping, error) {
   124  	return s.tenantMappingRepo.List(ctx)
   125  }
   126  
   127  // ListsByExternalIDs returns all tenants for provided external IDs.
   128  func (s *service) ListsByExternalIDs(ctx context.Context, ids []string) ([]*model.BusinessTenantMapping, error) {
   129  	return s.tenantMappingRepo.ListByExternalTenants(ctx, ids)
   130  }
   131  
   132  // ListsByType returns all tenants for provided external IDs.
   133  func (s *service) ListByType(ctx context.Context, tenantType tenantpkg.Type) ([]*model.BusinessTenantMapping, error) {
   134  	return s.tenantMappingRepo.ListByType(ctx, tenantType)
   135  }
   136  
   137  // ListPageBySearchTerm returns all tenants present in the Compass storage.
   138  func (s *service) ListPageBySearchTerm(ctx context.Context, searchTerm string, pageSize int, cursor string) (*model.BusinessTenantMappingPage, error) {
   139  	return s.tenantMappingRepo.ListPageBySearchTerm(ctx, searchTerm, pageSize, cursor)
   140  }
   141  
   142  // GetTenantByExternalID returns the tenant with the provided external ID.
   143  func (s *service) GetTenantByExternalID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) {
   144  	return s.tenantMappingRepo.GetByExternalTenant(ctx, id)
   145  }
   146  
   147  // GetTenantByID returns the tenant with the provided ID.
   148  func (s *service) GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) {
   149  	return s.tenantMappingRepo.Get(ctx, id)
   150  }
   151  
   152  // GetLowestOwnerForResource returns the lowest tenant in the hierarchy that is owner of a given resource.
   153  func (s *service) GetLowestOwnerForResource(ctx context.Context, resourceType resource.Type, objectID string) (string, error) {
   154  	return s.tenantMappingRepo.GetLowestOwnerForResource(ctx, resourceType, objectID)
   155  }
   156  
   157  // MultipleToTenantMapping assigns a new internal ID to all the provided tenants, and returns the BusinessTenantMappingInputs as BusinessTenantMappings.
   158  func (s *service) MultipleToTenantMapping(tenantInputs []model.BusinessTenantMappingInput) []model.BusinessTenantMapping {
   159  	tenants := make([]model.BusinessTenantMapping, 0, len(tenantInputs))
   160  	tenantIDs := make(map[string]string, len(tenantInputs))
   161  	for _, tenant := range tenantInputs {
   162  		id := s.uidService.Generate()
   163  		tenants = append(tenants, *tenant.ToBusinessTenantMapping(id))
   164  		tenantIDs[tenant.ExternalTenant] = id
   165  	}
   166  	for i := 0; i < len(tenants); i++ { // Convert parent ID from external to internal id reference
   167  		if len(tenants[i].Parent) > 0 {
   168  			if _, ok := tenantIDs[tenants[i].Parent]; ok { // If the parent is inserted in this request (otherwise we assume that it is already in the db)
   169  				tenants[i].Parent = tenantIDs[tenants[i].Parent]
   170  
   171  				var moved bool
   172  				tenants, moved = MoveBeforeIfShould(tenants, tenants[i].Parent, i) // Move my parent before me (to be inserted first) if it is not already
   173  				if moved {
   174  					i-- // Process the moved parent as well
   175  				}
   176  			}
   177  		}
   178  	}
   179  	return tenants
   180  }
   181  
   182  // Update updates tenant
   183  func (s *service) Update(ctx context.Context, id string, tenantInput model.BusinessTenantMappingInput) error {
   184  	tenant := tenantInput.ToBusinessTenantMapping(id)
   185  
   186  	if err := s.tenantMappingRepo.Update(ctx, tenant); err != nil {
   187  		return errors.Wrapf(err, "while updating tenant with id %s", id)
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // GetCustomerIDParentRecursively gets the top parent external ID (customer_id) for a given tenant
   194  func (s *service) GetCustomerIDParentRecursively(ctx context.Context, tenantID string) (string, error) {
   195  	return s.tenantMappingRepo.GetCustomerIDParentRecursively(ctx, tenantID)
   196  }
   197  
   198  // CreateTenantAccessForResource creates a tenant access for a single resource.Type
   199  func (s *service) CreateTenantAccessForResource(ctx context.Context, tenantAccess *model.TenantAccess) error {
   200  	resourceType := tenantAccess.ResourceType
   201  	m2mTable, ok := resourceType.TenantAccessTable()
   202  	if !ok {
   203  		return errors.Errorf("entity %q does not have access table", resourceType)
   204  	}
   205  
   206  	ta := s.converter.TenantAccessToEntity(tenantAccess)
   207  
   208  	if err := repo.CreateSingleTenantAccess(ctx, m2mTable, ta); err != nil {
   209  		return errors.Wrapf(err, "while creating tenant acccess for resource type %q with ID %q for tenant %q", string(resourceType), ta.ResourceID, ta.TenantID)
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // CreateTenantAccessForResourceRecursively creates a tenant access for a single resource.Type recursively
   216  func (s *service) CreateTenantAccessForResourceRecursively(ctx context.Context, tenantAccess *model.TenantAccess) error {
   217  	resourceType := tenantAccess.ResourceType
   218  	m2mTable, ok := resourceType.TenantAccessTable()
   219  	if !ok {
   220  		return errors.Errorf("entity %q does not have access table", resourceType)
   221  	}
   222  
   223  	ta := s.converter.TenantAccessToEntity(tenantAccess)
   224  
   225  	if err := repo.CreateTenantAccessRecursively(ctx, m2mTable, ta); err != nil {
   226  		return errors.Wrapf(err, "while creating tenant acccess for resource type %q with ID %q for tenant %q", string(resourceType), ta.ResourceID, ta.TenantID)
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // DeleteTenantAccessForResourceRecursively deletes a tenant access for a single resource.Type recursively
   233  func (s *service) DeleteTenantAccessForResourceRecursively(ctx context.Context, tenantAccess *model.TenantAccess) error {
   234  	resourceType := tenantAccess.ResourceType
   235  	m2mTable, ok := resourceType.TenantAccessTable()
   236  	if !ok {
   237  		return errors.Errorf("entity %q does not have access table", resourceType)
   238  	}
   239  
   240  	ta := s.converter.TenantAccessToEntity(tenantAccess)
   241  
   242  	if err := repo.DeleteTenantAccessRecursively(ctx, m2mTable, tenantAccess.InternalTenantID, []string{tenantAccess.ResourceID}); err != nil {
   243  		return errors.Wrapf(err, "while deleting tenant acccess for resource type %q with ID %q for tenant %q", string(resourceType), ta.ResourceID, ta.TenantID)
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // GetTenantAccessForResource gets a tenant access record for the specified resource
   250  func (s *service) GetTenantAccessForResource(ctx context.Context, tenantID, resourceID string, resourceType resource.Type) (*model.TenantAccess, error) {
   251  	m2mTable, ok := resourceType.TenantAccessTable()
   252  	if !ok {
   253  		return nil, errors.Errorf("entity %q does not have access table", resourceType)
   254  	}
   255  
   256  	ta, err := repo.GetSingleTenantAccess(ctx, m2mTable, tenantID, resourceID)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	tenantAccessModel := s.converter.TenantAccessFromEntity(ta)
   262  	tenantAccessModel.ResourceType = resourceType
   263  
   264  	return tenantAccessModel, nil
   265  }
   266  
   267  // ListByParentAndType list tenants by parent ID and tenant.Type
   268  func (s *service) ListByParentAndType(ctx context.Context, parentID string, tenantType tenantpkg.Type) ([]*model.BusinessTenantMapping, error) {
   269  	return s.tenantMappingRepo.ListByParentAndType(ctx, parentID, tenantType)
   270  }
   271  
   272  // ExtractTenantIDForTenantScopedFormationTemplates returns the tenant ID based on its type:
   273  //  1. If it's a SA -> return its parent GA id
   274  //  2. If it's any other tenant type -> return its ID
   275  func (s *service) ExtractTenantIDForTenantScopedFormationTemplates(ctx context.Context) (string, error) {
   276  	internalTenantID, err := s.getTenantFromContext(ctx)
   277  	if err != nil {
   278  		return "", err
   279  	}
   280  
   281  	if internalTenantID == "" {
   282  		return "", nil
   283  	}
   284  
   285  	tenantObject, err := s.GetTenantByID(ctx, internalTenantID)
   286  	if err != nil {
   287  		return "", err
   288  	}
   289  
   290  	if tenantObject.Type == tenantpkg.Subaccount {
   291  		return tenantObject.Parent, nil
   292  	}
   293  
   294  	return tenantObject.ID, nil
   295  }
   296  
   297  // getTenantFromContext validates and returns the tenant present in the context:
   298  //   - if both internalID and externalID are present -> proceed with tenant scoped formation templates (return the internalID from ctx)
   299  //   - if both internalID and externalID are NOT present -> -> proceed with global formation templates (return empty id)
   300  //   - otherwise return TenantNotFoundError
   301  func (s *service) getTenantFromContext(ctx context.Context) (string, error) {
   302  	tntCtx, err := LoadTenantPairFromContextNoChecks(ctx)
   303  	if err != nil {
   304  		return "", err
   305  	}
   306  
   307  	if tntCtx.InternalID != "" && tntCtx.ExternalID != "" {
   308  		return tntCtx.InternalID, nil
   309  	}
   310  
   311  	if tntCtx.InternalID == "" && tntCtx.ExternalID == "" {
   312  		return "", nil
   313  	}
   314  
   315  	return "", apperrors.NewTenantNotFoundError(tntCtx.ExternalID)
   316  }
   317  
   318  // CreateManyIfNotExists creates all provided tenants if they do not exist.
   319  // It creates or updates the subdomain and region labels of the provided tenants, no matter if they are pre-existing or not.
   320  func (s *labeledService) CreateManyIfNotExists(ctx context.Context, tenantInputs ...model.BusinessTenantMappingInput) ([]string, error) {
   321  	return s.upsertTenants(ctx, tenantInputs, s.tenantMappingRepo.UnsafeCreate)
   322  }
   323  
   324  // UpsertMany creates all provided tenants if they do not exist. If they do exist, they are internally updated.
   325  // It creates or updates the subdomain and region labels of the provided tenants, no matter if they are pre-existing or not.
   326  func (s *labeledService) UpsertMany(ctx context.Context, tenantInputs ...model.BusinessTenantMappingInput) ([]string, error) {
   327  	return s.upsertTenants(ctx, tenantInputs, s.tenantMappingRepo.Upsert)
   328  }
   329  
   330  // UpsertSingle creates a provided tenant if it does not exist. If it does exist, it is internally updated.
   331  // It creates or updates the subdomain and region labels of the provided tenant, no matter if it is pre-existing or not.
   332  func (s *labeledService) UpsertSingle(ctx context.Context, tenantInput model.BusinessTenantMappingInput) (string, error) {
   333  	return s.upsertTenant(ctx, tenantInput, s.tenantMappingRepo.Upsert)
   334  }
   335  
   336  func (s *labeledService) upsertTenant(ctx context.Context, tenantInput model.BusinessTenantMappingInput, upsertFunc func(context.Context, model.BusinessTenantMapping) error) (string, error) {
   337  	id := s.uidService.Generate()
   338  	tenant := *tenantInput.ToBusinessTenantMapping(id)
   339  	subdomains, regions := tenantLocality([]model.BusinessTenantMappingInput{tenantInput})
   340  
   341  	subdomain := ""
   342  	region := ""
   343  	if s, ok := subdomains[tenant.ExternalTenant]; ok {
   344  		subdomain = s
   345  	}
   346  	if r, ok := regions[tenant.ExternalTenant]; ok {
   347  		region = r
   348  	}
   349  
   350  	tenantID, err := s.createIfNotExists(ctx, tenant, subdomain, region, upsertFunc)
   351  	if err != nil {
   352  		return "", errors.Wrapf(err, "while creating tenant with external ID %s", tenant.ExternalTenant)
   353  	}
   354  
   355  	return tenantID, nil
   356  }
   357  
   358  func (s *labeledService) upsertTenants(ctx context.Context, tenantInputs []model.BusinessTenantMappingInput, upsertFunc func(context.Context, model.BusinessTenantMapping) error) ([]string, error) {
   359  	tenants := s.MultipleToTenantMapping(tenantInputs)
   360  	subdomains, regions := tenantLocality(tenantInputs)
   361  	tenantIDs := make([]string, 0, len(tenants))
   362  
   363  	for tenantIdx, tenant := range tenants {
   364  		subdomain := ""
   365  		region := ""
   366  		if s, ok := subdomains[tenant.ExternalTenant]; ok {
   367  			subdomain = s
   368  		}
   369  		if r, ok := regions[tenant.ExternalTenant]; ok {
   370  			region = r
   371  		}
   372  		tenantID, err := s.createIfNotExists(ctx, tenant, subdomain, region, upsertFunc)
   373  		if err != nil {
   374  			return nil, errors.Wrapf(err, "while creating tenant with external ID %s", tenant.ExternalTenant)
   375  		}
   376  		// the tenant already exists in our DB with a different ID, and we should update all child resources to use the correct internal ID
   377  		tenantIDs = append(tenantIDs, tenantID)
   378  		if tenantID != tenant.ID {
   379  			for i := tenantIdx; i < len(tenants); i++ {
   380  				if tenants[i].Parent == tenant.ID {
   381  					tenants[i].Parent = tenantID
   382  				}
   383  			}
   384  		}
   385  	}
   386  
   387  	return tenantIDs, nil
   388  }
   389  
   390  func (s *labeledService) createIfNotExists(ctx context.Context, tenant model.BusinessTenantMapping, subdomain, region string, action func(context.Context, model.BusinessTenantMapping) error) (string, error) {
   391  	if err := action(ctx, tenant); err != nil {
   392  		return "", err
   393  	}
   394  
   395  	tenantFromDB, err := s.tenantMappingRepo.GetByExternalTenant(ctx, tenant.ExternalTenant)
   396  	if err != nil {
   397  		return "", errors.Wrapf(err, "while retrieving the internal tenant ID of tenant with external ID %s", tenant.ExternalTenant)
   398  	}
   399  
   400  	return tenantFromDB.ID, s.upsertLabels(ctx, tenantFromDB.ID, subdomain, region, str.PtrStrToStr(tenant.LicenseType))
   401  }
   402  
   403  func (s *labeledService) upsertLabels(ctx context.Context, tenantID, subdomain, region, licenseType string) error {
   404  	if len(subdomain) > 0 {
   405  		if err := s.upsertLabel(ctx, tenantID, SubdomainLabelKey, subdomain); err != nil {
   406  			return errors.Wrapf(err, "while setting subdomain label for tenant with ID %s", tenantID)
   407  		}
   408  	}
   409  	if len(region) > 0 {
   410  		if err := s.upsertLabel(ctx, tenantID, RegionLabelKey, region); err != nil {
   411  			return errors.Wrapf(err, "while setting subdomain label for tenant with ID %s", tenantID)
   412  		}
   413  	}
   414  	if len(licenseType) > 0 {
   415  		if err := s.upsertLabel(ctx, tenantID, LicenseTypeLabelKey, licenseType); err != nil {
   416  			return errors.Wrapf(err, "while setting licenseType label for tenant with ID %s", tenantID)
   417  		}
   418  	}
   419  	return nil
   420  }
   421  
   422  func tenantLocality(tenants []model.BusinessTenantMappingInput) (map[string]string, map[string]string) {
   423  	subdomains := make(map[string]string)
   424  	regions := make(map[string]string)
   425  	for _, t := range tenants {
   426  		if len(t.Subdomain) > 0 {
   427  			subdomains[t.ExternalTenant] = t.Subdomain
   428  		}
   429  		if len(t.Region) > 0 {
   430  			regions[t.ExternalTenant] = t.Region
   431  		}
   432  	}
   433  
   434  	return subdomains, regions
   435  }
   436  
   437  // DeleteMany removes all tenants with the provided external tenant ids from the Compass storage.
   438  func (s *service) DeleteMany(ctx context.Context, externalTenantIDs []string) error {
   439  	for _, externalTenantID := range externalTenantIDs {
   440  		err := s.tenantMappingRepo.DeleteByExternalTenant(ctx, externalTenantID)
   441  		if err != nil {
   442  			return errors.Wrap(err, "while deleting tenant")
   443  		}
   444  	}
   445  
   446  	return nil
   447  }
   448  
   449  // ListLabels returns all labels directly linked to the given tenant, like subdomain or region.
   450  // That excludes labels of other resource types in the context of the given tenant, for example labels of an application in the given tenant - those labels are not returned.
   451  func (s *labeledService) ListLabels(ctx context.Context, tenantID string) (map[string]*model.Label, error) {
   452  	log.C(ctx).Infof("getting labels for tenant with ID %s", tenantID)
   453  	if err := s.ensureTenantExists(ctx, tenantID); err != nil {
   454  		return nil, err
   455  	}
   456  
   457  	labels, err := s.labelRepo.ListForObject(ctx, tenantID, model.TenantLabelableObject, tenantID)
   458  	if err != nil {
   459  		return nil, errors.Wrapf(err, "whilie listing labels for tenant with ID %s", tenantID)
   460  	}
   461  
   462  	return labels, nil
   463  }
   464  
   465  func (s *labeledService) upsertLabel(ctx context.Context, tenantID, key, value string) error {
   466  	label := &model.LabelInput{
   467  		Key:        key,
   468  		Value:      value,
   469  		ObjectID:   tenantID,
   470  		ObjectType: model.TenantLabelableObject,
   471  	}
   472  	return s.labelUpsertSvc.UpsertLabel(ctx, tenantID, label)
   473  }
   474  
   475  func (s *service) ensureTenantExists(ctx context.Context, id string) error {
   476  	exists, err := s.tenantMappingRepo.Exists(ctx, id)
   477  	if err != nil {
   478  		return errors.Wrapf(err, "while checking if tenant with ID %s exists", id)
   479  	}
   480  
   481  	if !exists {
   482  		return apperrors.NewNotFoundError(resource.Tenant, id)
   483  	}
   484  
   485  	return nil
   486  }
   487  
   488  // MoveBeforeIfShould moves the tenant with id right before index only if it is not already before it
   489  func MoveBeforeIfShould(tenants []model.BusinessTenantMapping, id string, indx int) ([]model.BusinessTenantMapping, bool) {
   490  	var itemIndex int
   491  	for i, tenant := range tenants {
   492  		if tenant.ID == id {
   493  			itemIndex = i
   494  		}
   495  	}
   496  
   497  	if itemIndex <= indx { // already before indx
   498  		return tenants, false
   499  	}
   500  
   501  	newTenants := make([]model.BusinessTenantMapping, 0, len(tenants))
   502  	for i := range tenants {
   503  		if i == itemIndex {
   504  			continue
   505  		}
   506  		if i == indx {
   507  			newTenants = append(newTenants, tenants[itemIndex], tenants[i])
   508  			continue
   509  		}
   510  		newTenants = append(newTenants, tenants[i])
   511  	}
   512  	return newTenants, true
   513  }