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

     1  package eventdef
     2  
     3  import (
     4  	"context"
     5  	"github.com/kyma-incubator/compass/components/director/pkg/str"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/log"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    10  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    13  	"github.com/kyma-incubator/compass/components/director/internal/timestamp"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/model"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  // EventAPIRepository is responsible for the repo-layer EventDefinition operations.
    20  //
    21  //go:generate mockery --name=EventAPIRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    22  type EventAPIRepository interface {
    23  	GetByID(ctx context.Context, tenantID string, id string) (*model.EventDefinition, error)
    24  	GetByIDGlobal(ctx context.Context, id string) (*model.EventDefinition, error)
    25  	GetForBundle(ctx context.Context, tenant string, id string, bundleID string) (*model.EventDefinition, error)
    26  	ListByBundleIDs(ctx context.Context, tenantID string, bundleIDs []string, bundleRefs []*model.BundleReference, totalCounts map[string]int, pageSize int, cursor string) ([]*model.EventDefinitionPage, error)
    27  	ListByResourceID(ctx context.Context, tenantID, resourceID string, resourceType resource.Type) ([]*model.EventDefinition, error)
    28  	ListByApplicationIDPage(ctx context.Context, tenantID string, appID string, pageSize int, cursor string) (*model.EventDefinitionPage, error)
    29  	Create(ctx context.Context, tenant string, item *model.EventDefinition) error
    30  	CreateGlobal(ctx context.Context, item *model.EventDefinition) error
    31  	Update(ctx context.Context, tenant string, item *model.EventDefinition) error
    32  	UpdateGlobal(ctx context.Context, item *model.EventDefinition) error
    33  	Delete(ctx context.Context, tenantID string, id string) error
    34  	DeleteGlobal(ctx context.Context, id string) error
    35  	DeleteAllByBundleID(ctx context.Context, tenantID, bundleID string) error
    36  }
    37  
    38  // UIDService is responsible for generating GUIDs, which will be used as internal eventDefinition IDs when they are created.
    39  //
    40  //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string
    41  type UIDService interface {
    42  	Generate() string
    43  }
    44  
    45  // SpecService is responsible for the service-layer Specification operations.
    46  //
    47  //go:generate mockery --name=SpecService --output=automock --outpkg=automock --case=underscore --disable-version-string
    48  type SpecService interface {
    49  	CreateByReferenceObjectID(ctx context.Context, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (string, error)
    50  	UpdateByReferenceObjectID(ctx context.Context, id string, in model.SpecInput, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) error
    51  	GetByReferenceObjectID(ctx context.Context, resourceType resource.Type, objectType model.SpecReferenceObjectType, objectID string) (*model.Spec, error)
    52  	RefetchSpec(ctx context.Context, id string, objectType model.SpecReferenceObjectType) (*model.Spec, error)
    53  	ListFetchRequestsByReferenceObjectIDs(ctx context.Context, tenant string, objectIDs []string, objectType model.SpecReferenceObjectType) ([]*model.FetchRequest, error)
    54  }
    55  
    56  // BundleReferenceService is responsible for the service-layer BundleReference operations.
    57  //
    58  //go:generate mockery --name=BundleReferenceService --output=automock --outpkg=automock --case=underscore --disable-version-string
    59  type BundleReferenceService interface {
    60  	GetForBundle(ctx context.Context, objectType model.BundleReferenceObjectType, objectID, bundleID *string) (*model.BundleReference, error)
    61  	CreateByReferenceObjectID(ctx context.Context, in model.BundleReferenceInput, objectType model.BundleReferenceObjectType, objectID, bundleID *string) error
    62  	UpdateByReferenceObjectID(ctx context.Context, in model.BundleReferenceInput, objectType model.BundleReferenceObjectType, objectID, bundleID *string) error
    63  	DeleteByReferenceObjectID(ctx context.Context, objectType model.BundleReferenceObjectType, objectID, bundleID *string) error
    64  	ListByBundleIDs(ctx context.Context, objectType model.BundleReferenceObjectType, bundleIDs []string, pageSize int, cursor string) ([]*model.BundleReference, map[string]int, error)
    65  }
    66  
    67  type service struct {
    68  	eventAPIRepo           EventAPIRepository
    69  	uidService             UIDService
    70  	specService            SpecService
    71  	bundleReferenceService BundleReferenceService
    72  	timestampGen           timestamp.Generator
    73  }
    74  
    75  // NewService returns a new object responsible for service-layer EventDefinition operations.
    76  func NewService(eventAPIRepo EventAPIRepository, uidService UIDService, specService SpecService, bundleReferenceService BundleReferenceService) *service {
    77  	return &service{
    78  		eventAPIRepo:           eventAPIRepo,
    79  		uidService:             uidService,
    80  		specService:            specService,
    81  		bundleReferenceService: bundleReferenceService,
    82  		timestampGen:           timestamp.DefaultGenerator,
    83  	}
    84  }
    85  
    86  // ListByBundleIDs lists all EventDefinitions in pages for a given array of bundle IDs.
    87  func (s *service) ListByBundleIDs(ctx context.Context, bundleIDs []string, pageSize int, cursor string) ([]*model.EventDefinitionPage, error) {
    88  	tnt, err := tenant.LoadFromContext(ctx)
    89  	if err != nil {
    90  		return nil, errors.Wrap(err, "while loading tenant from context")
    91  	}
    92  
    93  	if pageSize < 1 || pageSize > 200 {
    94  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
    95  	}
    96  
    97  	bundleRefs, counts, err := s.bundleReferenceService.ListByBundleIDs(ctx, model.BundleEventReference, bundleIDs, pageSize, cursor)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return s.eventAPIRepo.ListByBundleIDs(ctx, tnt, bundleIDs, bundleRefs, counts, pageSize, cursor)
   103  }
   104  
   105  // ListByApplicationID lists all EventDefinitions for a given application ID.
   106  func (s *service) ListByApplicationID(ctx context.Context, appID string) ([]*model.EventDefinition, error) {
   107  	tnt, err := tenant.LoadFromContext(ctx)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	return s.eventAPIRepo.ListByResourceID(ctx, tnt, appID, resource.Application)
   113  }
   114  
   115  // ListByApplicationTemplateVersionID lists all EventDefinitions for a given application template version ID.
   116  func (s *service) ListByApplicationTemplateVersionID(ctx context.Context, appTemplateVersionID string) ([]*model.EventDefinition, error) {
   117  	return s.eventAPIRepo.ListByResourceID(ctx, "", appTemplateVersionID, resource.ApplicationTemplateVersion)
   118  }
   119  
   120  // ListByApplicationIDPage lists all EventDefinitions for a given application ID with paging.
   121  func (s *service) ListByApplicationIDPage(ctx context.Context, appID string, pageSize int, cursor string) (*model.EventDefinitionPage, error) {
   122  	tnt, err := tenant.LoadFromContext(ctx)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	if pageSize < 1 || pageSize > 200 {
   128  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
   129  	}
   130  
   131  	return s.eventAPIRepo.ListByApplicationIDPage(ctx, tnt, appID, pageSize, cursor)
   132  }
   133  
   134  // Get returns the EventDefinition by its ID.
   135  func (s *service) Get(ctx context.Context, id string) (*model.EventDefinition, error) {
   136  	tnt, err := tenant.LoadFromContext(ctx)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	eventAPI, err := s.eventAPIRepo.GetByID(ctx, tnt, id)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return eventAPI, nil
   147  }
   148  
   149  // GetForBundle returns an EventDefinition by its ID and a bundle ID.
   150  func (s *service) GetForBundle(ctx context.Context, id string, bundleID string) (*model.EventDefinition, error) {
   151  	tnt, err := tenant.LoadFromContext(ctx)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	eventAPI, err := s.eventAPIRepo.GetForBundle(ctx, tnt, id, bundleID)
   157  	if err != nil {
   158  		return nil, errors.Wrap(err, "while getting API definition")
   159  	}
   160  
   161  	return eventAPI, nil
   162  }
   163  
   164  // CreateInBundle creates an EventDefinition. This function is used in the graphQL flow.
   165  func (s *service) CreateInBundle(ctx context.Context, resourceType resource.Type, resourceID string, bundleID string, in model.EventDefinitionInput, spec *model.SpecInput) (string, error) {
   166  	return s.Create(ctx, resourceType, resourceID, &bundleID, nil, in, []*model.SpecInput{spec}, nil, 0, "")
   167  }
   168  
   169  // CreateInApplication creates an EventDefinition in the context of an Application without Bundle
   170  func (s *service) CreateInApplication(ctx context.Context, appID string, in model.EventDefinitionInput, spec *model.SpecInput) (string, error) {
   171  	return s.Create(ctx, resource.Application, appID, nil, nil, in, []*model.SpecInput{spec}, nil, 0, "")
   172  }
   173  
   174  // Create creates EventDefinition/s. This function is used both in the ORD scenario and is re-used in CreateInBundle but with "null" ORD specific arguments.
   175  func (s *service) Create(ctx context.Context, resourceType resource.Type, resourceID string, bundleID, packageID *string, in model.EventDefinitionInput, specs []*model.SpecInput, bundleIDs []string, eventHash uint64, defaultBundleID string) (string, error) {
   176  	id := s.uidService.Generate()
   177  	eventAPI := in.ToEventDefinition(id, resourceType, resourceID, packageID, eventHash)
   178  
   179  	if err := s.createEventDef(ctx, eventAPI, resourceType); err != nil {
   180  		return "", errors.Wrap(err, "while creating api")
   181  	}
   182  
   183  	if err := s.processSpecs(ctx, eventAPI.ID, specs, resourceType); err != nil {
   184  		return "", err
   185  	}
   186  
   187  	if err := s.createBundleReferenceObject(ctx, eventAPI.ID, bundleID, defaultBundleID, bundleIDs); err != nil {
   188  		return "", err
   189  	}
   190  
   191  	return id, nil
   192  }
   193  
   194  // Update updates an EventDefinition. This function is used in the graphQL flow.
   195  func (s *service) Update(ctx context.Context, resourceType resource.Type, id string, in model.EventDefinitionInput, specIn *model.SpecInput) error {
   196  	return s.UpdateInManyBundles(ctx, resourceType, id, in, specIn, nil, nil, nil, 0, "")
   197  }
   198  
   199  // UpdateInManyBundles updates EventDefinition/s. This function is used both in the ORD scenario and is re-used in Update but with "null" ORD specific arguments.
   200  func (s *service) UpdateInManyBundles(ctx context.Context, resourceType resource.Type, id string, in model.EventDefinitionInput, specIn *model.SpecInput, bundleIDsFromBundleReference, bundleIDsForCreation, bundleIDsForDeletion []string, eventHash uint64, defaultBundleID string) error {
   201  	eventDef, err := s.getEventDef(ctx, id, resourceType)
   202  	if err != nil {
   203  		return errors.Wrapf(err, "while getting EventDefinition with ID %s", id)
   204  	}
   205  
   206  	resourceID := getParentResourceID(eventDef)
   207  	eventDef = in.ToEventDefinition(id, resourceType, resourceID, eventDef.PackageID, eventHash)
   208  
   209  	if err = s.updateEventDef(ctx, eventDef, resourceType); err != nil {
   210  		return errors.Wrapf(err, "while updating EventDefinition with ID %s", id)
   211  	}
   212  
   213  	if err = s.handleReferenceObjects(ctx, eventDef.ID, bundleIDsForCreation, bundleIDsForDeletion, bundleIDsFromBundleReference, defaultBundleID); err != nil {
   214  		return errors.Wrapf(err, "while handling reference objects for EventDefinition with ID %s", eventDef.ID)
   215  	}
   216  
   217  	if specIn != nil {
   218  		return s.handleSpecsInEvent(ctx, resourceType, eventDef.ID, specIn)
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // UpdateForApplication updates an EventDefinition for Application without being in a Bundle
   225  func (s *service) UpdateForApplication(ctx context.Context, id string, in model.EventDefinitionInput, specIn *model.SpecInput) error {
   226  	tnt, err := tenant.LoadFromContext(ctx)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	event, err := s.Get(ctx, id)
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	event = in.ToEventDefinition(id, resource.Application, str.PtrStrToStr(event.ApplicationID), event.PackageID, 0)
   237  
   238  	if err = s.eventAPIRepo.Update(ctx, tnt, event); err != nil {
   239  		return errors.Wrapf(err, "while updating EventDefinition with id %s", id)
   240  	}
   241  
   242  	if specIn != nil {
   243  		return s.handleSpecsInEvent(ctx, resource.Application, id, specIn)
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // Delete deletes the EventDefinition by its ID.
   250  func (s *service) Delete(ctx context.Context, resourceType resource.Type, id string) error {
   251  	if err := s.deleteEventDef(ctx, id, resourceType); err != nil {
   252  		return errors.Wrapf(err, "while deleting EventDefinition with id %s", id)
   253  	}
   254  
   255  	log.C(ctx).Infof("Successfully deleted EventDefinition with id %s", id)
   256  
   257  	return nil
   258  }
   259  
   260  // DeleteAllByBundleID deletes all EventDefinitions for a given bundle ID
   261  func (s *service) DeleteAllByBundleID(ctx context.Context, bundleID string) error {
   262  	tnt, err := tenant.LoadFromContext(ctx)
   263  	if err != nil {
   264  		return errors.Wrapf(err, "while loading tenant from context")
   265  	}
   266  
   267  	err = s.eventAPIRepo.DeleteAllByBundleID(ctx, tnt, bundleID)
   268  	if err != nil {
   269  		return errors.Wrapf(err, "while deleting EventDefinitions for Bundle with id %q", bundleID)
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  // ListFetchRequests lists all FetchRequests for given specification IDs
   276  func (s *service) ListFetchRequests(ctx context.Context, specIDs []string) ([]*model.FetchRequest, error) {
   277  	tnt, err := tenant.LoadFromContext(ctx)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	fetchRequests, err := s.specService.ListFetchRequestsByReferenceObjectIDs(ctx, tnt, specIDs, model.EventSpecReference)
   283  	if err != nil {
   284  		if apperrors.IsNotFoundError(err) {
   285  			return nil, nil
   286  		}
   287  		return nil, err
   288  	}
   289  
   290  	return fetchRequests, nil
   291  }
   292  
   293  func (s *service) handleSpecsInEvent(ctx context.Context, resourceType resource.Type, id string, specIn *model.SpecInput) error {
   294  	dbSpec, err := s.specService.GetByReferenceObjectID(ctx, resourceType, model.EventSpecReference, id)
   295  	if err != nil {
   296  		return errors.Wrapf(err, "while getting spec for EventDefinition with id %q", id)
   297  	}
   298  
   299  	if dbSpec == nil {
   300  		_, err = s.specService.CreateByReferenceObjectID(ctx, *specIn, resourceType, model.EventSpecReference, id)
   301  		return errors.Wrapf(err, "while creating spec for EventDefinition with ID %s", id)
   302  	}
   303  
   304  	if err = s.specService.UpdateByReferenceObjectID(ctx, dbSpec.ID, *specIn, resourceType, model.EventSpecReference, id); err != nil {
   305  		return errors.Wrapf(err, "while updating spec for EventDefinition with ID %s", id)
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  func (s *service) handleReferenceObjects(ctx context.Context, id string, bundleIDsForCreation, bundleIDsForDeletion, bundleIDsFromBundleReference []string, defaultBundleID string) error {
   312  	for _, bundleID := range bundleIDsForCreation {
   313  		createBundleRefInput := &model.BundleReferenceInput{}
   314  		if defaultBundleID != "" && bundleID == defaultBundleID {
   315  			isDefaultBundle := true
   316  			createBundleRefInput = &model.BundleReferenceInput{IsDefaultBundle: &isDefaultBundle}
   317  		}
   318  		if err := s.bundleReferenceService.CreateByReferenceObjectID(ctx, *createBundleRefInput, model.BundleEventReference, &id, &bundleID); err != nil {
   319  			return err
   320  		}
   321  	}
   322  
   323  	for _, bundleID := range bundleIDsForDeletion {
   324  		if err := s.bundleReferenceService.DeleteByReferenceObjectID(ctx, model.BundleEventReference, &id, &bundleID); err != nil {
   325  			return err
   326  		}
   327  	}
   328  
   329  	for _, bundleID := range bundleIDsFromBundleReference {
   330  		bundleRefInput := &model.BundleReferenceInput{}
   331  		if defaultBundleID != "" && bundleID == defaultBundleID {
   332  			isDefaultBundle := true
   333  			bundleRefInput = &model.BundleReferenceInput{IsDefaultBundle: &isDefaultBundle}
   334  		}
   335  		if err := s.bundleReferenceService.UpdateByReferenceObjectID(ctx, *bundleRefInput, model.BundleEventReference, &id, &bundleID); err != nil {
   336  			return err
   337  		}
   338  	}
   339  
   340  	return nil
   341  }
   342  
   343  func (s *service) processSpecs(ctx context.Context, eventID string, specs []*model.SpecInput, resourceType resource.Type) error {
   344  	for _, spec := range specs {
   345  		if spec == nil {
   346  			continue
   347  		}
   348  
   349  		if _, err := s.specService.CreateByReferenceObjectID(ctx, *spec, resourceType, model.EventSpecReference, eventID); err != nil {
   350  			return err
   351  		}
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  func (s *service) createBundleReferenceObject(ctx context.Context, eventID string, bundleID *string, defaultBundleID string, bundleIDs []string) error {
   358  	if bundleIDs == nil && bundleID != nil {
   359  		if err := s.bundleReferenceService.CreateByReferenceObjectID(ctx, model.BundleReferenceInput{}, model.BundleEventReference, &eventID, bundleID); err != nil {
   360  			return err
   361  		}
   362  	}
   363  
   364  	for _, bndlID := range bundleIDs {
   365  		bundleRefInput := &model.BundleReferenceInput{}
   366  		if defaultBundleID != "" && bndlID == defaultBundleID {
   367  			isDefaultBundle := true
   368  			bundleRefInput = &model.BundleReferenceInput{
   369  				IsDefaultBundle: &isDefaultBundle,
   370  			}
   371  		}
   372  		if err := s.bundleReferenceService.CreateByReferenceObjectID(ctx, *bundleRefInput, model.BundleEventReference, &eventID, &bndlID); err != nil {
   373  			return err
   374  		}
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  func (s *service) createEventDef(ctx context.Context, eventAPI *model.EventDefinition, resourceType resource.Type) error {
   381  	if resourceType.IsTenantIgnorable() {
   382  		return s.eventAPIRepo.CreateGlobal(ctx, eventAPI)
   383  	}
   384  
   385  	tnt, err := tenant.LoadFromContext(ctx)
   386  	if err != nil {
   387  		return err
   388  	}
   389  
   390  	return s.eventAPIRepo.Create(ctx, tnt, eventAPI)
   391  }
   392  
   393  func (s *service) getEventDef(ctx context.Context, id string, resourceType resource.Type) (*model.EventDefinition, error) {
   394  	if resourceType.IsTenantIgnorable() {
   395  		return s.eventAPIRepo.GetByIDGlobal(ctx, id)
   396  	}
   397  
   398  	return s.Get(ctx, id)
   399  }
   400  
   401  func (s *service) updateEventDef(ctx context.Context, eventDef *model.EventDefinition, resourceType resource.Type) error {
   402  	if resourceType.IsTenantIgnorable() {
   403  		return s.eventAPIRepo.UpdateGlobal(ctx, eventDef)
   404  	}
   405  
   406  	tnt, err := tenant.LoadFromContext(ctx)
   407  	if err != nil {
   408  		return err
   409  	}
   410  
   411  	return s.eventAPIRepo.Update(ctx, tnt, eventDef)
   412  }
   413  
   414  func (s *service) deleteEventDef(ctx context.Context, id string, resourceType resource.Type) error {
   415  	if resourceType.IsTenantIgnorable() {
   416  		return s.eventAPIRepo.DeleteGlobal(ctx, id)
   417  	}
   418  
   419  	tnt, err := tenant.LoadFromContext(ctx)
   420  	if err != nil {
   421  		return errors.Wrapf(err, "while loading tenant from context")
   422  	}
   423  
   424  	return s.eventAPIRepo.Delete(ctx, tnt, id)
   425  }
   426  
   427  func getParentResourceID(api *model.EventDefinition) string {
   428  	if api.ApplicationTemplateVersionID != nil {
   429  		return *api.ApplicationTemplateVersionID
   430  	} else if api.ApplicationID != nil {
   431  		return *api.ApplicationID
   432  	}
   433  
   434  	return ""
   435  }