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

     1  package tenant
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/internal/repo"
     7  
     8  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
     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/graphql"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    14  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    15  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    16  
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // BusinessTenantMappingService is responsible for the service-layer tenant operations.
    21  //go:generate mockery --name=BusinessTenantMappingService --output=automock --outpkg=automock --case=underscore --disable-version-string
    22  type BusinessTenantMappingService interface {
    23  	List(ctx context.Context) ([]*model.BusinessTenantMapping, error)
    24  	ListPageBySearchTerm(ctx context.Context, searchTerm string, pageSize int, cursor string) (*model.BusinessTenantMappingPage, error)
    25  	ListLabels(ctx context.Context, tenantID string) (map[string]*model.Label, error)
    26  	GetTenantByExternalID(ctx context.Context, externalID string) (*model.BusinessTenantMapping, error)
    27  	GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error)
    28  	UpsertMany(ctx context.Context, tenantInputs ...model.BusinessTenantMappingInput) ([]string, error)
    29  	UpsertSingle(ctx context.Context, tenantInput model.BusinessTenantMappingInput) (string, error)
    30  	Update(ctx context.Context, id string, tenantInput model.BusinessTenantMappingInput) error
    31  	DeleteMany(ctx context.Context, tenantInputs []string) error
    32  	GetLowestOwnerForResource(ctx context.Context, resourceType resource.Type, objectID string) (string, error)
    33  	GetInternalTenant(ctx context.Context, externalTenant string) (string, error)
    34  	CreateTenantAccessForResourceRecursively(ctx context.Context, tenantAccess *model.TenantAccess) error
    35  	DeleteTenantAccessForResourceRecursively(ctx context.Context, tenantAccess *model.TenantAccess) error
    36  	GetTenantAccessForResource(ctx context.Context, tenantID, resourceID string, resourceType resource.Type) (*model.TenantAccess, error)
    37  }
    38  
    39  // BusinessTenantMappingConverter is used to convert the internally used tenant representation model.BusinessTenantMapping
    40  // into the external GraphQL representation graphql.Tenant.
    41  //go:generate mockery --name=BusinessTenantMappingConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    42  type BusinessTenantMappingConverter interface {
    43  	MultipleToGraphQL(in []*model.BusinessTenantMapping) []*graphql.Tenant
    44  	MultipleInputFromGraphQL(in []*graphql.BusinessTenantMappingInput) []model.BusinessTenantMappingInput
    45  	InputFromGraphQL(tnt graphql.BusinessTenantMappingInput) model.BusinessTenantMappingInput
    46  	ToGraphQL(in *model.BusinessTenantMapping) *graphql.Tenant
    47  	TenantAccessInputFromGraphQL(in graphql.TenantAccessInput) (*model.TenantAccess, error)
    48  	TenantAccessToGraphQL(in *model.TenantAccess) (*graphql.TenantAccess, error)
    49  	TenantAccessToEntity(in *model.TenantAccess) *repo.TenantAccess
    50  	TenantAccessFromEntity(in *repo.TenantAccess) *model.TenantAccess
    51  }
    52  
    53  // Resolver is the resolver responsible for tenant-related GraphQL requests.
    54  type Resolver struct {
    55  	transact persistence.Transactioner
    56  
    57  	srv     BusinessTenantMappingService
    58  	conv    BusinessTenantMappingConverter
    59  	fetcher Fetcher
    60  }
    61  
    62  // NewResolver returns the GraphQL resolver for tenants.
    63  func NewResolver(transact persistence.Transactioner, srv BusinessTenantMappingService, conv BusinessTenantMappingConverter, fetcher Fetcher) *Resolver {
    64  	return &Resolver{
    65  		transact: transact,
    66  		srv:      srv,
    67  		conv:     conv,
    68  		fetcher:  fetcher,
    69  	}
    70  }
    71  
    72  // Tenants transactionally retrieves a page of tenants present in the Compass storage by a search term. If the search term is missing it will be ignored in the resulting tenant subset.
    73  func (r *Resolver) Tenants(ctx context.Context, first *int, after *graphql.PageCursor, searchTerm *string) (*graphql.TenantPage, error) {
    74  	tx, err := r.transact.Begin()
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
    79  
    80  	var cursor string
    81  	if after != nil {
    82  		cursor = string(*after)
    83  	}
    84  	if first == nil {
    85  		return nil, apperrors.NewInvalidDataError("missing required parameter 'first'")
    86  	}
    87  
    88  	searchStr := str.PtrStrToStr(searchTerm)
    89  
    90  	ctx = persistence.SaveToContext(ctx, tx)
    91  
    92  	tenantsPage, err := r.srv.ListPageBySearchTerm(ctx, searchStr, *first, cursor)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	if err = tx.Commit(); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	gqlTenants := r.conv.MultipleToGraphQL(tenantsPage.Data)
   102  
   103  	return &graphql.TenantPage{
   104  		Data:       gqlTenants,
   105  		TotalCount: tenantsPage.TotalCount,
   106  		PageInfo: &graphql.PageInfo{
   107  			StartCursor: graphql.PageCursor(tenantsPage.PageInfo.StartCursor),
   108  			EndCursor:   graphql.PageCursor(tenantsPage.PageInfo.EndCursor),
   109  			HasNextPage: tenantsPage.PageInfo.HasNextPage,
   110  		},
   111  	}, nil
   112  }
   113  
   114  // Tenant first checks whether a tenant with the provided external ID exists in the Compass DB.
   115  // If it doesn't, it calls an API which fetches details for the given tenant from an external tenancy service,
   116  // stores the tenant in the Compass DB and returns 200 OK if the tenant was successfully created.
   117  // Finally, it retrieves a tenant with the provided external ID from the Compass storage.
   118  func (r *Resolver) Tenant(ctx context.Context, externalID string) (*graphql.Tenant, error) {
   119  	tx, err := r.transact.Begin()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   124  
   125  	ctx = persistence.SaveToContext(ctx, tx)
   126  
   127  	tenant, err := r.srv.GetTenantByExternalID(ctx, externalID)
   128  	if err != nil && apperrors.IsNotFoundError(err) {
   129  		tx, err = r.fetchTenant(tx, externalID)
   130  		if err != nil {
   131  			log.C(ctx).Error(err)
   132  			return nil, apperrors.NewNotFoundError(resource.Tenant, externalID)
   133  		}
   134  		ctx = persistence.SaveToContext(ctx, tx)
   135  		tenant, err = r.srv.GetTenantByExternalID(ctx, externalID)
   136  	}
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	if err = tx.Commit(); err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	return r.conv.ToGraphQL(tenant), nil
   146  }
   147  
   148  // TenantByID retrieves a tenant with the provided internal ID from the Compass storage.
   149  func (r *Resolver) TenantByID(ctx context.Context, internalID string) (*graphql.Tenant, error) {
   150  	tx, err := r.transact.Begin()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   155  
   156  	ctx = persistence.SaveToContext(ctx, tx)
   157  
   158  	t, err := r.srv.GetTenantByID(ctx, internalID)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	if err = tx.Commit(); err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	gqlTenant := r.conv.ToGraphQL(t)
   167  	return gqlTenant, nil
   168  }
   169  
   170  // TenantByLowestOwnerForResource retrieves a tenant with the provided internal ID from the Compass storage.
   171  func (r *Resolver) TenantByLowestOwnerForResource(ctx context.Context, resourceStr, objectID string) (string, error) {
   172  	tx, err := r.transact.Begin()
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   177  
   178  	ctx = persistence.SaveToContext(ctx, tx)
   179  
   180  	resourceType := resource.Type(resourceStr)
   181  
   182  	tenantID, err := r.srv.GetLowestOwnerForResource(ctx, resourceType, objectID)
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  	if err = tx.Commit(); err != nil {
   187  		return "", err
   188  	}
   189  
   190  	return tenantID, nil
   191  }
   192  
   193  // Labels transactionally retrieves all existing labels of the given tenant if it exists.
   194  func (r *Resolver) Labels(ctx context.Context, obj *graphql.Tenant, key *string) (graphql.Labels, error) {
   195  	if obj == nil {
   196  		return nil, apperrors.NewInternalError("Tenant cannot be empty")
   197  	}
   198  	log.C(ctx).Infof("getting labels for tenant with ID %s, and internal ID %s", obj.ID, obj.InternalID)
   199  
   200  	tx, err := r.transact.Begin()
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   205  
   206  	ctx = persistence.SaveToContext(ctx, tx)
   207  
   208  	itemMap, err := r.srv.ListLabels(ctx, obj.InternalID)
   209  	if err != nil {
   210  		if apperrors.IsNotFoundError(err) {
   211  			return nil, tx.Commit()
   212  		}
   213  		return nil, err
   214  	}
   215  
   216  	if err = tx.Commit(); err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	resultLabels := make(map[string]interface{})
   221  	for _, label := range itemMap {
   222  		if key == nil || label.Key == *key {
   223  			resultLabels[label.Key] = label.Value
   224  		}
   225  	}
   226  
   227  	return resultLabels, nil
   228  }
   229  
   230  // Write creates new global and subaccounts
   231  func (r *Resolver) Write(ctx context.Context, inputTenants []*graphql.BusinessTenantMappingInput) ([]string, error) {
   232  	tx, err := r.transact.Begin()
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   237  
   238  	ctx = persistence.SaveToContext(ctx, tx)
   239  
   240  	tenants := r.conv.MultipleInputFromGraphQL(inputTenants)
   241  
   242  	tenantIDs, err := r.srv.UpsertMany(ctx, tenants...)
   243  	if err != nil {
   244  		return nil, errors.Wrap(err, "while writing new tenants")
   245  	}
   246  
   247  	if err = tx.Commit(); err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return tenantIDs, nil
   252  }
   253  
   254  // WriteSingle creates a single tenant
   255  func (r *Resolver) WriteSingle(ctx context.Context, inputTenant graphql.BusinessTenantMappingInput) (string, error) {
   256  	tx, err := r.transact.Begin()
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   261  
   262  	ctx = persistence.SaveToContext(ctx, tx)
   263  
   264  	tenant := r.conv.InputFromGraphQL(inputTenant)
   265  
   266  	id, err := r.srv.UpsertSingle(ctx, tenant)
   267  	if err != nil {
   268  		return "", errors.Wrapf(err, "while writing a new tenant %q", inputTenant.ExternalTenant)
   269  	}
   270  
   271  	if err = tx.Commit(); err != nil {
   272  		return "", err
   273  	}
   274  
   275  	return id, nil
   276  }
   277  
   278  // Delete deletes tenants
   279  func (r *Resolver) Delete(ctx context.Context, externalTenantIDs []string) (int, error) {
   280  	tx, err := r.transact.Begin()
   281  	if err != nil {
   282  		return -1, err
   283  	}
   284  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   285  
   286  	ctx = persistence.SaveToContext(ctx, tx)
   287  
   288  	if err := r.srv.DeleteMany(ctx, externalTenantIDs); err != nil {
   289  		return -1, errors.Wrap(err, "while deleting tenants")
   290  	}
   291  
   292  	if err = tx.Commit(); err != nil {
   293  		return -1, err
   294  	}
   295  
   296  	return len(externalTenantIDs), nil
   297  }
   298  
   299  // Update update single tenant
   300  func (r *Resolver) Update(ctx context.Context, id string, in graphql.BusinessTenantMappingInput) (*graphql.Tenant, error) {
   301  	tx, err := r.transact.Begin()
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   306  
   307  	ctx = persistence.SaveToContext(ctx, tx)
   308  	tenantModels := r.conv.MultipleInputFromGraphQL([]*graphql.BusinessTenantMappingInput{&in})
   309  	if err := r.srv.Update(ctx, id, tenantModels[0]); err != nil {
   310  		return nil, errors.Wrapf(err, "while updating tenant with internal ID %s and external ID %s", id, in.ExternalTenant)
   311  	}
   312  
   313  	tenant, err := r.srv.GetTenantByExternalID(ctx, in.ExternalTenant)
   314  	if err != nil {
   315  		return nil, errors.Wrapf(err, "while getting tenant with external id %s", in.ExternalTenant)
   316  	}
   317  
   318  	if err = tx.Commit(); err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	return r.conv.ToGraphQL(tenant), nil
   323  }
   324  
   325  func (r *Resolver) fetchTenant(tx persistence.PersistenceTx, externalID string) (persistence.PersistenceTx, error) {
   326  	if err := tx.Commit(); err != nil {
   327  		return nil, err
   328  	}
   329  	if err := r.fetcher.FetchOnDemand(externalID, ""); err != nil { // will always fail
   330  		return nil, errors.Wrapf(err, "while trying to create if not exists tenant %s", externalID)
   331  	}
   332  	tr, err := r.transact.Begin()
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	return tr, nil
   337  }
   338  
   339  // AddTenantAccess adds a tenant access record for tenantID about resourceID
   340  func (r *Resolver) AddTenantAccess(ctx context.Context, in graphql.TenantAccessInput) (*graphql.TenantAccess, error) {
   341  	tx, err := r.transact.Begin()
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   346  
   347  	ctx = persistence.SaveToContext(ctx, tx)
   348  
   349  	tenantAccess, err := r.conv.TenantAccessInputFromGraphQL(in)
   350  	if err != nil {
   351  		return nil, errors.Wrapf(err, "while converting tenant access input for tenant %q about resource %q of type %q", in.TenantID, in.ResourceID, in.ResourceType)
   352  	}
   353  
   354  	internalTenant, err := r.srv.GetInternalTenant(ctx, tenantAccess.ExternalTenantID)
   355  	if err != nil {
   356  		return nil, errors.Wrapf(err, "while getting internal tenant for external tenant ID: %q", tenantAccess.ExternalTenantID)
   357  	}
   358  	tenantAccess.InternalTenantID = internalTenant
   359  
   360  	if err := r.srv.CreateTenantAccessForResourceRecursively(ctx, tenantAccess); err != nil {
   361  		return nil, errors.Wrapf(err, "while creating tenant access record for tenant %q about resource %q of type %q", tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   362  	}
   363  
   364  	storedTenantAccess, err := r.srv.GetTenantAccessForResource(ctx, tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   365  	if err != nil {
   366  		return nil, errors.Wrapf(err, "while fetching stored tenant access for tenant %q about resource %q of type %q", tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   367  	}
   368  	storedTenantAccess.ExternalTenantID = tenantAccess.ExternalTenantID
   369  
   370  	output, err := r.conv.TenantAccessToGraphQL(storedTenantAccess)
   371  	if err != nil {
   372  		return nil, errors.Wrapf(err, "while converting to graphql tenant access for tenant %q about resource %q of type %q", tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   373  	}
   374  
   375  	if err = tx.Commit(); err != nil {
   376  		return nil, err
   377  	}
   378  
   379  	return output, nil
   380  }
   381  
   382  // RemoveTenantAccess removes the tenant access record for tenantID about resourceID
   383  func (r *Resolver) RemoveTenantAccess(ctx context.Context, tenantID, resourceID string, resourceType graphql.TenantAccessObjectType) (*graphql.TenantAccess, error) {
   384  	tx, err := r.transact.Begin()
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   389  
   390  	ctx = persistence.SaveToContext(ctx, tx)
   391  
   392  	internalTenantID, err := r.srv.GetInternalTenant(ctx, tenantID)
   393  	if err != nil {
   394  		return nil, errors.Wrapf(err, "while getting internal tenant for external tenant ID: %q", tenantID)
   395  	}
   396  
   397  	resourceTypeModel, err := fromTenantAccessObjectTypeToResourceType(resourceType)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  
   402  	tenantAccess, err := r.srv.GetTenantAccessForResource(ctx, internalTenantID, resourceID, resourceTypeModel)
   403  	if err != nil {
   404  		if apperrors.IsNotFoundError(err) {
   405  			return nil, apperrors.NewNotFoundErrorWithType(resource.TenantAccess)
   406  		}
   407  
   408  		return nil, errors.Wrapf(err, "while fetching stored tenant access for tenant %q about resource %q of type %q", internalTenantID, resourceID, resourceTypeModel)
   409  	}
   410  	tenantAccess.ExternalTenantID = tenantID
   411  
   412  	if err := r.srv.DeleteTenantAccessForResourceRecursively(ctx, tenantAccess); err != nil {
   413  		return nil, errors.Wrapf(err, "while deleting tenant access record for tenant %q about resource %q of type %q", tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   414  	}
   415  
   416  	output, err := r.conv.TenantAccessToGraphQL(tenantAccess)
   417  	if err != nil {
   418  		return nil, errors.Wrapf(err, "while converting to graphql tenant access for tenant %q about resource %q of type %q", tenantAccess.InternalTenantID, tenantAccess.ResourceID, tenantAccess.ResourceType)
   419  	}
   420  
   421  	if err = tx.Commit(); err != nil {
   422  		return nil, err
   423  	}
   424  
   425  	return output, nil
   426  }