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

     1  package spec
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    10  	"github.com/kyma-incubator/compass/components/director/internal/timestamp"
    11  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/kyma-incubator/compass/components/director/internal/model"
    15  )
    16  
    17  // SpecRepository missing godoc
    18  //
    19  //go:generate mockery --name=SpecRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    20  type SpecRepository interface {
    21  	Create(ctx context.Context, tenant string, item *model.Spec) error
    22  	CreateGlobal(ctx context.Context, item *model.Spec) error
    23  	GetByID(ctx context.Context, tenantID string, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error)
    24  	GetByIDGlobal(ctx context.Context, id string) (*model.Spec, error)
    25  	ListIDByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) ([]string, error)
    26  	ListIDByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]string, error)
    27  	ListByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error)
    28  	ListByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error)
    29  	ListByReferenceObjectIDs(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectIDs []string) ([]*model.Spec, error)
    30  	Delete(ctx context.Context, tenant, id string, objectType model.SpecReferenceObjectType) error
    31  	DeleteByReferenceObjectID(ctx context.Context, tenant string, objectType model.SpecReferenceObjectType, objectID string) error
    32  	DeleteByReferenceObjectIDGlobal(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) error
    33  	Update(ctx context.Context, tenant string, item *model.Spec) error
    34  	UpdateGlobal(ctx context.Context, item *model.Spec) error
    35  	Exists(ctx context.Context, tenantID, id string, objectType model.SpecReferenceObjectType) (bool, error)
    36  }
    37  
    38  // FetchRequestRepository missing godoc
    39  //
    40  //go:generate mockery --name=FetchRequestRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    41  type FetchRequestRepository interface {
    42  	Create(ctx context.Context, tenant string, item *model.FetchRequest) error
    43  	CreateGlobal(ctx context.Context, item *model.FetchRequest) error
    44  	GetByReferenceObjectID(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectID string) (*model.FetchRequest, error)
    45  	DeleteByReferenceObjectID(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectID string) error
    46  	DeleteByReferenceObjectIDGlobal(ctx context.Context, objectType model.FetchRequestReferenceObjectType, objectID string) error
    47  	ListByReferenceObjectIDs(ctx context.Context, tenant string, objectType model.FetchRequestReferenceObjectType, objectIDs []string) ([]*model.FetchRequest, error)
    48  	ListByReferenceObjectIDsGlobal(ctx context.Context, objectType model.FetchRequestReferenceObjectType, objectIDs []string) ([]*model.FetchRequest, error)
    49  }
    50  
    51  // UIDService missing godoc
    52  //
    53  //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string
    54  type UIDService interface {
    55  	Generate() string
    56  }
    57  
    58  // FetchRequestService missing godoc
    59  //
    60  //go:generate mockery --name=FetchRequestService --output=automock --outpkg=automock --case=underscore --disable-version-string
    61  type FetchRequestService interface {
    62  	HandleSpec(ctx context.Context, fr *model.FetchRequest) *string
    63  }
    64  
    65  type service struct {
    66  	repo                SpecRepository
    67  	fetchRequestRepo    FetchRequestRepository
    68  	uidService          UIDService
    69  	fetchRequestService FetchRequestService
    70  	timestampGen        timestamp.Generator
    71  }
    72  
    73  // NewService missing godoc
    74  func NewService(repo SpecRepository, fetchRequestRepo FetchRequestRepository, uidService UIDService, fetchRequestService FetchRequestService) *service {
    75  	return &service{
    76  		repo:                repo,
    77  		fetchRequestRepo:    fetchRequestRepo,
    78  		uidService:          uidService,
    79  		fetchRequestService: fetchRequestService,
    80  		timestampGen:        timestamp.DefaultGenerator,
    81  	}
    82  }
    83  
    84  // GetByID takes care of retrieving a specific spec entity from db based on a provided id and objectType (API or Event)
    85  func (s *service) GetByID(ctx context.Context, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error) {
    86  	tnt, err := tenant.LoadFromContext(ctx)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return s.repo.GetByID(ctx, tnt, id, objectType)
    92  }
    93  
    94  func (s *service) GetByIDGlobal(ctx context.Context, id string) (*model.Spec, error) {
    95  	return s.repo.GetByIDGlobal(ctx, id)
    96  }
    97  
    98  // ListByReferenceObjectID missing godoc
    99  func (s *service) ListByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string) ([]*model.Spec, error) {
   100  	tnt, err := tenant.LoadFromContext(ctx)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return s.repo.ListByReferenceObjectID(ctx, tnt, objectType, objectID)
   106  }
   107  
   108  // ListIDByReferenceObjectID retrieves all spec ids by objectType and objectID
   109  func (s *service) ListIDByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) ([]string, error) {
   110  	if resourceType.IsTenantIgnorable() {
   111  		return s.repo.ListIDByReferenceObjectIDGlobal(ctx, objectType, objectID)
   112  	}
   113  
   114  	tnt, err := tenant.LoadFromContext(ctx)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return s.repo.ListIDByReferenceObjectID(ctx, tnt, objectType, objectID)
   120  }
   121  
   122  // GetByReferenceObjectID
   123  // Until now APIs and Events had embedded specification in them, we will model this behavior by relying that the first created spec is the one which GraphQL expects
   124  func (s *service) GetByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (*model.Spec, error) {
   125  	specs, err := s.listSpecsByReferenceObjectID(ctx, objectType, objectID, resourceType)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	if len(specs) > 0 {
   131  		return specs[0], nil
   132  	}
   133  
   134  	return nil, nil
   135  }
   136  
   137  // ListByReferenceObjectIDs missing godoc
   138  func (s *service) ListByReferenceObjectIDs(ctx context.Context, objectType model.SpecReferenceObjectType, objectIDs []string) ([]*model.Spec, error) {
   139  	tnt, err := tenant.LoadFromContext(ctx)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	specs, err := s.repo.ListByReferenceObjectIDs(ctx, tnt, objectType, objectIDs)
   145  
   146  	return specs, err
   147  }
   148  
   149  // CreateByReferenceObjectID missing godoc
   150  func (s *service) CreateByReferenceObjectID(ctx context.Context, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (string, error) {
   151  	id := s.uidService.Generate()
   152  	spec, err := in.ToSpec(id, objectType, objectID)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  
   157  	if err = s.createSpec(ctx, spec, resourceType); err != nil {
   158  		return "", errors.Wrapf(err, "while creating spec for %q with id %q", objectType, objectID)
   159  	}
   160  
   161  	if in.Data == nil && in.FetchRequest != nil {
   162  		fr, err := s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType)
   163  		if err != nil {
   164  			return "", errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id)
   165  		}
   166  
   167  		spec.Data = s.fetchRequestService.HandleSpec(ctx, fr)
   168  
   169  		if err = s.updateSpec(ctx, spec, resourceType); err != nil {
   170  			return "", errors.Wrapf(err, "while updating %s Specification with id %q", objectType, id)
   171  		}
   172  	}
   173  
   174  	return id, nil
   175  }
   176  
   177  // CreateByReferenceObjectIDWithDelayedFetchRequest identical to CreateByReferenceObjectID with the only difference that the spec and fetch request entities are only persisted in DB and the fetch request itself is not executed
   178  func (s *service) CreateByReferenceObjectIDWithDelayedFetchRequest(ctx context.Context, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (string, *model.FetchRequest, error) {
   179  	id := s.uidService.Generate()
   180  	spec, err := in.ToSpec(id, objectType, objectID)
   181  	if err != nil {
   182  		return "", nil, err
   183  	}
   184  
   185  	if err = s.createSpec(ctx, spec, resourceType); err != nil {
   186  		return "", nil, errors.Wrapf(err, "while creating spec for %q with id %q", objectType, objectID)
   187  	}
   188  
   189  	var fr *model.FetchRequest
   190  	if in.Data == nil && in.FetchRequest != nil {
   191  		fr, err = s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType)
   192  		if err != nil {
   193  			return "", nil, errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id)
   194  		}
   195  	}
   196  
   197  	return id, fr, nil
   198  }
   199  
   200  // UpdateByReferenceObjectID missing godoc
   201  func (s *service) UpdateByReferenceObjectID(ctx context.Context, id string, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) error {
   202  	if err := s.deleteFetchRequestByReferenceObjectID(ctx, id, objectType, resourceType); err != nil {
   203  		return errors.Wrapf(err, "while deleting FetchRequest for Specification with id %q", id)
   204  	}
   205  
   206  	spec, err := in.ToSpec(id, objectType, objectID)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	if in.Data == nil && in.FetchRequest != nil {
   212  		fr, err := s.createFetchRequest(ctx, *in.FetchRequest, id, objectType, resourceType)
   213  		if err != nil {
   214  			return errors.Wrapf(err, "while creating FetchRequest for %s Specification with id %q", objectType, id)
   215  		}
   216  
   217  		spec.Data = s.fetchRequestService.HandleSpec(ctx, fr)
   218  	}
   219  
   220  	if err = s.updateSpec(ctx, spec, resourceType); err != nil {
   221  		return errors.Wrapf(err, "while updating %s Specification with id %q", objectType, id)
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  // UpdateSpecOnly takes care of simply updating a single spec entity in db without looking and executing corresponding fetch requests that may be related to it
   228  func (s *service) UpdateSpecOnly(ctx context.Context, spec model.Spec) error {
   229  	tnt, err := tenant.LoadFromContext(ctx)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	if err = s.repo.Update(ctx, tnt, &spec); err != nil {
   235  		return errors.Wrapf(err, "while updating %s Specification with id %q", spec.ObjectType, spec.ID)
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  // UpdateSpecOnlyGlobal takes care of simply updating a single spec entity in db without looking and executing corresponding fetch requests that may be related to it
   242  func (s *service) UpdateSpecOnlyGlobal(ctx context.Context, spec model.Spec) error {
   243  	if err := s.repo.UpdateGlobal(ctx, &spec); err != nil {
   244  		return errors.Wrapf(err, "while updating %s Specification with id %q", spec.ObjectType, spec.ID)
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  // DeleteByReferenceObjectID missing godoc
   251  func (s *service) DeleteByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) error {
   252  	if err := s.deleteSpecByReferenceObjectID(ctx, objectType, objectID, resourceType); err != nil {
   253  		return errors.Wrapf(err, "while deleting reference object type %s with id %s", objectType, objectID)
   254  	}
   255  
   256  	return nil
   257  }
   258  
   259  // Delete missing godoc
   260  func (s *service) Delete(ctx context.Context, id string, objectType model.SpecReferenceObjectType) error {
   261  	tnt, err := tenant.LoadFromContext(ctx)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	err = s.repo.Delete(ctx, tnt, id, objectType)
   267  	if err != nil {
   268  		return errors.Wrapf(err, "while deleting Specification with id %q", id)
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  // RefetchSpec missing godoc
   275  func (s *service) RefetchSpec(ctx context.Context, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error) {
   276  	tnt, err := tenant.LoadFromContext(ctx)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	spec, err := s.repo.GetByID(ctx, tnt, id, objectType)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	fetchRequest, err := s.fetchRequestRepo.GetByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), id)
   287  	if err != nil && !apperrors.IsNotFoundError(err) {
   288  		return nil, errors.Wrapf(err, "while getting FetchRequest for Specification with id %q", id)
   289  	}
   290  
   291  	if fetchRequest != nil {
   292  		spec.Data = s.fetchRequestService.HandleSpec(ctx, fetchRequest)
   293  	}
   294  
   295  	if err = s.repo.Update(ctx, tnt, spec); err != nil {
   296  		return nil, errors.Wrapf(err, "while updating Specification with id %q", id)
   297  	}
   298  
   299  	return spec, nil
   300  }
   301  
   302  // GetFetchRequest missing godoc
   303  func (s *service) GetFetchRequest(ctx context.Context, specID string, objectType model.SpecReferenceObjectType) (*model.FetchRequest, error) {
   304  	tnt, err := tenant.LoadFromContext(ctx)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	exists, err := s.repo.Exists(ctx, tnt, specID, objectType)
   310  	if err != nil {
   311  		return nil, errors.Wrapf(err, "while checking if Specification with id %q exists", specID)
   312  	}
   313  	if !exists {
   314  		return nil, fmt.Errorf("specification with id %q doesn't exist", specID)
   315  	}
   316  
   317  	fetchRequest, err := s.fetchRequestRepo.GetByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), specID)
   318  	if err != nil {
   319  		return nil, errors.Wrapf(err, "while getting FetchRequest by Specification with id %q", specID)
   320  	}
   321  
   322  	return fetchRequest, nil
   323  }
   324  
   325  // ListFetchRequestsByReferenceObjectIDs lists specs by reference object IDs
   326  func (s *service) ListFetchRequestsByReferenceObjectIDs(ctx context.Context, tenant string, objectIDs []string, objectType model.SpecReferenceObjectType) ([]*model.FetchRequest, error) {
   327  	return s.fetchRequestRepo.ListByReferenceObjectIDs(ctx, tenant, getFetchRequestObjectTypeBySpecObjectType(objectType), objectIDs)
   328  }
   329  
   330  // ListFetchRequestsByReferenceObjectIDsGlobal lists specs by reference object IDs without tenant isolation
   331  func (s *service) ListFetchRequestsByReferenceObjectIDsGlobal(ctx context.Context, objectIDs []string, objectType model.SpecReferenceObjectType) ([]*model.FetchRequest, error) {
   332  	return s.fetchRequestRepo.ListByReferenceObjectIDsGlobal(ctx, getFetchRequestObjectTypeBySpecObjectType(objectType), objectIDs)
   333  }
   334  
   335  func (s *service) createFetchRequest(ctx context.Context, in model.FetchRequestInput, parentObjectID string, objectType model.SpecReferenceObjectType, resourceType resource.Type) (*model.FetchRequest, error) {
   336  	id := s.uidService.Generate()
   337  	fr := in.ToFetchRequest(s.timestampGen(), id, getFetchRequestObjectTypeBySpecObjectType(objectType), parentObjectID)
   338  
   339  	if resourceType.IsTenantIgnorable() {
   340  		if err := s.fetchRequestRepo.CreateGlobal(ctx, fr); err != nil {
   341  			return nil, err
   342  		}
   343  
   344  		return fr, nil
   345  	}
   346  
   347  	tnt, err := tenant.LoadFromContext(ctx)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	if err = s.fetchRequestRepo.Create(ctx, tnt, fr); err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	return fr, nil
   357  }
   358  
   359  func (s *service) listSpecsByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, resourceType resource.Type) ([]*model.Spec, error) {
   360  	if resourceType.IsTenantIgnorable() {
   361  		return s.repo.ListByReferenceObjectIDGlobal(ctx, objectType, objectID)
   362  	}
   363  
   364  	tnt, err := tenant.LoadFromContext(ctx)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  
   369  	return s.repo.ListByReferenceObjectID(ctx, tnt, objectType, objectID)
   370  }
   371  
   372  func (s *service) createSpec(ctx context.Context, spec *model.Spec, resourceType resource.Type) error {
   373  	if resourceType.IsTenantIgnorable() {
   374  		return s.repo.CreateGlobal(ctx, spec)
   375  	}
   376  
   377  	tnt, err := tenant.LoadFromContext(ctx)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	return s.repo.Create(ctx, tnt, spec)
   383  }
   384  
   385  func (s *service) updateSpec(ctx context.Context, spec *model.Spec, resourceType resource.Type) error {
   386  	if resourceType.IsTenantIgnorable() {
   387  		return s.repo.UpdateGlobal(ctx, spec)
   388  	}
   389  
   390  	tnt, err := tenant.LoadFromContext(ctx)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	return s.repo.Update(ctx, tnt, spec)
   396  }
   397  
   398  func (s *service) deleteSpecByReferenceObjectID(ctx context.Context, objectType model.SpecReferenceObjectType, objectID string, resourceType resource.Type) error {
   399  	if resourceType.IsTenantIgnorable() {
   400  		return s.repo.DeleteByReferenceObjectIDGlobal(ctx, objectType, objectID)
   401  	}
   402  
   403  	tnt, err := tenant.LoadFromContext(ctx)
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	return s.repo.DeleteByReferenceObjectID(ctx, tnt, objectType, objectID)
   409  }
   410  
   411  func (s *service) deleteFetchRequestByReferenceObjectID(ctx context.Context, id string, objectType model.SpecReferenceObjectType, resourceType resource.Type) error {
   412  	if resourceType.IsTenantIgnorable() {
   413  		if _, err := s.repo.GetByIDGlobal(ctx, id); err != nil {
   414  			return err
   415  		}
   416  
   417  		return s.fetchRequestRepo.DeleteByReferenceObjectIDGlobal(ctx, getFetchRequestObjectTypeBySpecObjectType(objectType), id)
   418  	}
   419  
   420  	tnt, err := tenant.LoadFromContext(ctx)
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	if _, err = s.repo.GetByID(ctx, tnt, id, objectType); err != nil {
   426  		return err
   427  	}
   428  
   429  	return s.fetchRequestRepo.DeleteByReferenceObjectID(ctx, tnt, getFetchRequestObjectTypeBySpecObjectType(objectType), id)
   430  }
   431  
   432  func getFetchRequestObjectTypeBySpecObjectType(specObjectType model.SpecReferenceObjectType) model.FetchRequestReferenceObjectType {
   433  	switch specObjectType {
   434  	case model.APISpecReference:
   435  		return model.APISpecFetchRequestReference
   436  	case model.EventSpecReference:
   437  		return model.EventSpecFetchRequestReference
   438  	}
   439  	return ""
   440  }