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

     1  package runtimectx
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/log"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    12  	"github.com/kyma-incubator/compass/components/director/internal/model"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  // RuntimeContextRepository missing godoc
    20  //go:generate mockery --name=RuntimeContextRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    21  type RuntimeContextRepository interface {
    22  	Exists(ctx context.Context, tenant, id string) (bool, error)
    23  	GetByID(ctx context.Context, tenant, id string) (*model.RuntimeContext, error)
    24  	GetForRuntime(ctx context.Context, tenant, id, runtimeID string) (*model.RuntimeContext, error)
    25  	List(ctx context.Context, runtimeID string, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error)
    26  	ListAllForRuntime(ctx context.Context, tenant, runtimeID string) ([]*model.RuntimeContext, error)
    27  	ListByRuntimeIDs(ctx context.Context, tenantID string, runtimeIDs []string, pageSize int, cursor string) ([]*model.RuntimeContextPage, error)
    28  	Create(ctx context.Context, tenant string, item *model.RuntimeContext) error
    29  	Update(ctx context.Context, tenant string, item *model.RuntimeContext) error
    30  	Delete(ctx context.Context, tenant, id string) error
    31  }
    32  
    33  // LabelRepository missing godoc
    34  //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    35  type LabelRepository interface {
    36  	ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error)
    37  }
    38  
    39  // RuntimeRepository is responsible for the repo-layer Runtime operations.
    40  //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    41  type RuntimeRepository interface {
    42  	OwnerExists(ctx context.Context, tenant, id string) (bool, error)
    43  }
    44  
    45  // LabelUpsertService missing godoc
    46  //go:generate mockery --name=LabelUpsertService --output=automock --outpkg=automock --case=underscore --disable-version-string
    47  type LabelUpsertService interface {
    48  	UpsertMultipleLabels(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labels map[string]interface{}) error
    49  	UpsertLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) error
    50  }
    51  
    52  //go:generate mockery --exported --name=formationService --output=automock --outpkg=automock --case=underscore --disable-version-string
    53  type formationService interface {
    54  	GetScenariosFromMatchingASAs(ctx context.Context, objectID string, objType graphql.FormationObjectType) ([]string, error)
    55  	GetFormationsForObject(ctx context.Context, tnt string, objType model.LabelableObject, objID string) ([]string, error)
    56  	AssignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
    57  	UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
    58  }
    59  
    60  //go:generate mockery --exported --name=tenantService --output=automock --outpkg=automock --case=underscore --disable-version-string
    61  type tenantService interface {
    62  	GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error)
    63  }
    64  
    65  // UIDService missing godoc
    66  //go:generate mockery --name=UIDService --output=automock --outpkg=automock --case=underscore --disable-version-string
    67  type UIDService interface {
    68  	Generate() string
    69  }
    70  
    71  type service struct {
    72  	repo               RuntimeContextRepository
    73  	labelRepo          LabelRepository
    74  	runtimeRepo        RuntimeRepository
    75  	formationService   formationService
    76  	tenantService      tenantService
    77  	labelUpsertService LabelUpsertService
    78  	uidService         UIDService
    79  }
    80  
    81  // NewService missing godoc
    82  func NewService(repo RuntimeContextRepository,
    83  	labelRepo LabelRepository,
    84  	runtimeRepo RuntimeRepository,
    85  	labelUpsertService LabelUpsertService,
    86  	formationService formationService,
    87  	tenantService tenantService,
    88  	uidService UIDService) *service {
    89  	return &service{
    90  		repo:               repo,
    91  		labelRepo:          labelRepo,
    92  		runtimeRepo:        runtimeRepo,
    93  		formationService:   formationService,
    94  		tenantService:      tenantService,
    95  		labelUpsertService: labelUpsertService,
    96  		uidService:         uidService,
    97  	}
    98  }
    99  
   100  // Exist checks if RuntimeContext with ID `id` exists
   101  func (s *service) Exist(ctx context.Context, id string) (bool, error) {
   102  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   103  	if err != nil {
   104  		return false, errors.Wrapf(err, "while loading tenant from context")
   105  	}
   106  
   107  	exist, err := s.repo.Exists(ctx, rtmCtxTenant, id)
   108  	if err != nil {
   109  		return false, errors.Wrapf(err, "while getting Runtime Context with ID %s", id)
   110  	}
   111  
   112  	return exist, nil
   113  }
   114  
   115  // Create creates RuntimeContext using `in`. Retrieves all formations from ASAs matching the RuntimeContext
   116  // and assigns it to each formation
   117  func (s *service) Create(ctx context.Context, in model.RuntimeContextInput) (string, error) {
   118  	log.C(ctx).Infof("Creating runtime context for runtime with ID: %q and key: %q and value: %q", in.RuntimeID, in.Key, in.Value)
   119  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   120  	if err != nil {
   121  		return "", errors.Wrapf(err, "while loading tenant from context")
   122  	}
   123  	id := s.uidService.Generate()
   124  	rtmCtx := in.ToRuntimeContext(id)
   125  
   126  	if err = s.repo.Create(ctx, rtmCtxTenant, rtmCtx); err != nil {
   127  		return "", errors.Wrapf(err, "while creating Runtime Context")
   128  	}
   129  
   130  	tnt, err := s.tenantService.GetTenantByID(ctx, rtmCtxTenant)
   131  	if err != nil {
   132  		return "", errors.Wrapf(err, "while getting tenant with id %s", rtmCtxTenant)
   133  	}
   134  
   135  	if len(tnt.Parent) != 0 {
   136  		ctxWithParentTenant := tenant.SaveToContext(ctx, tnt.Parent, "")
   137  		scenariosFromAssignments, err := s.formationService.GetScenariosFromMatchingASAs(ctxWithParentTenant, id, graphql.FormationObjectTypeRuntimeContext)
   138  		if err != nil {
   139  			return "", errors.Wrapf(err, "while getting formations from automatic scenario assignments")
   140  		}
   141  
   142  		if len(scenariosFromAssignments) == 0 {
   143  			log.C(ctx).Infof("No scenarios found for runtime context with ID: %q that matched ASA", id)
   144  			return id, nil
   145  		}
   146  
   147  		// If we have a runtime with runtime context(s) we need to assign only the runtime context(s) to the formation.
   148  		// But if we create ASA in the provider account before registering runtime, the runtime will be assigned to the formation.
   149  		// And then if we register runtime context for this runtime, the runtime context will be assigned to the formation as well.
   150  		ownedRuntimeExists, err := s.runtimeRepo.OwnerExists(ctx, rtmCtxTenant, in.RuntimeID)
   151  		if err != nil {
   152  			return "", errors.Wrapf(err, "while checking if runtime with id %q exists", in.RuntimeID)
   153  		}
   154  
   155  		for _, scenario := range scenariosFromAssignments {
   156  			if _, err := s.formationService.AssignFormation(ctxWithParentTenant, tnt.Parent, id, graphql.FormationObjectTypeRuntimeContext, model.Formation{Name: scenario}); err != nil {
   157  				return "", errors.Wrapf(err, "while assigning formation with name %q for runtime context", scenario)
   158  			}
   159  
   160  			if ownedRuntimeExists {
   161  				if _, err := s.formationService.UnassignFormation(ctxWithParentTenant, tnt.Parent, in.RuntimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}); err != nil {
   162  					return "", errors.Wrapf(err, "while assigning formation with name %q for runtime context", scenario)
   163  				}
   164  			}
   165  		}
   166  	}
   167  	return id, nil
   168  }
   169  
   170  // Update updates RuntimeContext with ID `id` using `in`
   171  func (s *service) Update(ctx context.Context, id string, in model.RuntimeContextInput) error {
   172  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   173  	if err != nil {
   174  		return errors.Wrapf(err, "while loading tenant from context")
   175  	}
   176  
   177  	if _, err = s.repo.GetByID(ctx, rtmCtxTenant, id); err != nil {
   178  		return errors.Wrapf(err, "while getting Runtime Context with id %s", id)
   179  	}
   180  
   181  	rtmCtx := in.ToRuntimeContext(id)
   182  
   183  	if err = s.repo.Update(ctx, rtmCtxTenant, rtmCtx); err != nil {
   184  		return errors.Wrapf(err, "while updating Runtime Context with id %s", id)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // Delete unassigns the RuntimeContext from each formation that it is part of and then deletes it
   191  func (s *service) Delete(ctx context.Context, id string) error {
   192  	rtmTenant, err := tenant.LoadFromContext(ctx)
   193  	if err != nil {
   194  		return errors.Wrapf(err, "while loading tenant from context")
   195  	}
   196  
   197  	formations, err := s.formationService.GetFormationsForObject(ctx, rtmTenant, model.RuntimeContextLabelableObject, id)
   198  	if err != nil && !apperrors.IsNotFoundError(err) {
   199  		return errors.Wrapf(err, "while listing formations for runtime context with id %q", id)
   200  	}
   201  
   202  	for _, f := range formations {
   203  		if _, err := s.formationService.UnassignFormation(ctx, rtmTenant, id, graphql.FormationObjectTypeRuntimeContext, model.Formation{Name: f}); err != nil {
   204  			return errors.Wrapf(err, "while unassigning formation with name %q for runtime context", f)
   205  		}
   206  	}
   207  
   208  	err = s.repo.Delete(ctx, rtmTenant, id)
   209  	if err != nil {
   210  		return errors.Wrapf(err, "while deleting Runtime Context with id %s", id)
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // GetByID retrieves the RuntimeContext with the provided `id`
   217  func (s *service) GetByID(ctx context.Context, id string) (*model.RuntimeContext, error) {
   218  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   219  	if err != nil {
   220  		return nil, errors.Wrapf(err, "while loading tenant from context")
   221  	}
   222  
   223  	runtimeCtx, err := s.repo.GetByID(ctx, rtmCtxTenant, id)
   224  	if err != nil {
   225  		return nil, errors.Wrapf(err, "while getting Runtime Context with ID %s", id)
   226  	}
   227  
   228  	return runtimeCtx, nil
   229  }
   230  
   231  // GetForRuntime retrieves the RuntimeContext with the provided `id` associated with Runtime with id `runtimeID`
   232  func (s *service) GetForRuntime(ctx context.Context, id, runtimeID string) (*model.RuntimeContext, error) {
   233  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   234  	if err != nil {
   235  		return nil, errors.Wrapf(err, "while loading tenant from context")
   236  	}
   237  
   238  	runtimeCtx, err := s.repo.GetForRuntime(ctx, rtmCtxTenant, id, runtimeID)
   239  	if err != nil {
   240  		return nil, errors.Wrapf(err, "while getting Runtime Context with ID %s", id)
   241  	}
   242  
   243  	return runtimeCtx, nil
   244  }
   245  
   246  // ListAllForRuntime retrieves all RuntimeContexts for Runtime with id `runtimeID`
   247  func (s *service) ListAllForRuntime(ctx context.Context, runtimeID string) ([]*model.RuntimeContext, error) {
   248  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   249  	if err != nil {
   250  		return nil, errors.Wrapf(err, "while loading tenant from context")
   251  	}
   252  
   253  	runtimeCtxs, err := s.repo.ListAllForRuntime(ctx, rtmCtxTenant, runtimeID)
   254  	if err != nil {
   255  		return nil, errors.Wrapf(err, "while listing Runtime Contexts fot Runtime with ID %s", runtimeID)
   256  	}
   257  
   258  	return runtimeCtxs, nil
   259  }
   260  
   261  // ListByFilter retrieves a page of RuntimeContext objects associated to Runtime with id `runtimeID` that are matching the provided filters
   262  func (s *service) ListByFilter(ctx context.Context, runtimeID string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error) {
   263  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   264  	if err != nil {
   265  		return nil, errors.Wrapf(err, "while loading tenant from context")
   266  	}
   267  
   268  	if pageSize < 1 || pageSize > 200 {
   269  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
   270  	}
   271  
   272  	return s.repo.List(ctx, runtimeID, rtmCtxTenant, filter, pageSize, cursor)
   273  }
   274  
   275  // ListByRuntimeIDs retrieves a page of RuntimeContext objects for each runtimeID
   276  func (s *service) ListByRuntimeIDs(ctx context.Context, runtimeIDs []string, pageSize int, cursor string) ([]*model.RuntimeContextPage, error) {
   277  	tnt, err := tenant.LoadFromContext(ctx)
   278  	if err != nil {
   279  		return nil, errors.Wrapf(err, "while loading tenant from context")
   280  	}
   281  
   282  	if pageSize < 1 || pageSize > 200 {
   283  		return nil, apperrors.NewInvalidDataError("page size must be between 1 and 200")
   284  	}
   285  
   286  	return s.repo.ListByRuntimeIDs(ctx, tnt, runtimeIDs, pageSize, cursor)
   287  }
   288  
   289  // ListLabels lists Labels for RuntimeContext with ID `runtimeCtxID`
   290  func (s *service) ListLabels(ctx context.Context, runtimeCtxID string) (map[string]*model.Label, error) {
   291  	rtmCtxTenant, err := tenant.LoadFromContext(ctx)
   292  	if err != nil {
   293  		return nil, errors.Wrapf(err, "while loading tenant from context")
   294  	}
   295  
   296  	rtmCtxExists, err := s.repo.Exists(ctx, rtmCtxTenant, runtimeCtxID)
   297  	if err != nil {
   298  		return nil, errors.Wrapf(err, "while checking Runtime Context existence with id %s", runtimeCtxID)
   299  	}
   300  
   301  	if !rtmCtxExists {
   302  		return nil, fmt.Errorf("runtime Context with ID %s doesn't exist", runtimeCtxID)
   303  	}
   304  
   305  	labels, err := s.labelRepo.ListForObject(ctx, rtmCtxTenant, model.RuntimeContextLabelableObject, runtimeCtxID)
   306  	if err != nil {
   307  		return nil, errors.Wrapf(err, "while getting label for Runtime Context with id %s", runtimeCtxID)
   308  	}
   309  
   310  	return labels, nil
   311  }