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 }