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

     1  package label
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
     7  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    12  
    13  	"github.com/kyma-incubator/compass/components/director/internal/model"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const (
    18  	tableName                   string = "public.labels"
    19  	tenantColumn                string = "tenant_id"
    20  	keyColumn                   string = "key"
    21  	runtimeContextTable         string = "public.tenant_runtime_contexts"
    22  	businessTenantMappingsTable string = `public.business_tenant_mappings`
    23  )
    24  
    25  var (
    26  	tableColumns       = []string{"id", tenantColumn, "app_id", "runtime_id", "runtime_context_id", "app_template_id", keyColumn, "value", "version"}
    27  	updatableColumns   = []string{"value"}
    28  	idColumns          = []string{"id"}
    29  	versionedIDColumns = append(idColumns, "version")
    30  )
    31  
    32  // Converter missing godoc
    33  //
    34  //go:generate mockery --name=Converter --output=automock --outpkg=automock --case=underscore --disable-version-string
    35  type Converter interface {
    36  	ToEntity(in *model.Label) (*Entity, error)
    37  	FromEntity(in *Entity) (*model.Label, error)
    38  }
    39  
    40  type repository struct {
    41  	lister        repo.Lister
    42  	listerGlobal  repo.ListerGlobal
    43  	deleter       repo.Deleter
    44  	deleterGlobal repo.DeleterGlobal
    45  	getter        repo.SingleGetter
    46  	getterGlobal  repo.SingleGetterGlobal
    47  
    48  	runtimeContextQueryBuilder         repo.QueryBuilderGlobal
    49  	businessTenantMappingsQueryBuilder repo.QueryBuilderGlobal
    50  
    51  	embeddedTenantLister  repo.Lister
    52  	embeddedTenantDeleter repo.Deleter
    53  	embeddedTenantGetter  repo.SingleGetter
    54  
    55  	creator                        repo.Creator
    56  	globalCreator                  repo.CreatorGlobal
    57  	updater                        repo.Updater
    58  	versionedUpdater               repo.Updater
    59  	updaterGlobal                  repo.UpdaterGlobal
    60  	embeddedTenantUpdater          repo.UpdaterGlobal
    61  	versionedEmbeddedTenantUpdater repo.UpdaterGlobal
    62  	conv                           Converter
    63  }
    64  
    65  // NewRepository missing godoc
    66  func NewRepository(conv Converter) *repository {
    67  	return &repository{
    68  		lister:        repo.NewLister(tableName, tableColumns),
    69  		listerGlobal:  repo.NewListerGlobal(resource.Label, tableName, tableColumns),
    70  		deleter:       repo.NewDeleter(tableName),
    71  		deleterGlobal: repo.NewDeleterGlobal(resource.Label, tableName),
    72  		getter:        repo.NewSingleGetter(tableName, tableColumns),
    73  		getterGlobal:  repo.NewSingleGetterGlobal(resource.Label, tableName, tableColumns),
    74  
    75  		runtimeContextQueryBuilder:         repo.NewQueryBuilderGlobal(resource.RuntimeContext, runtimeContextTable, []string{"tenant_id"}),
    76  		businessTenantMappingsQueryBuilder: repo.NewQueryBuilderGlobal(resource.Tenant, businessTenantMappingsTable, []string{"id"}),
    77  
    78  		embeddedTenantLister:  repo.NewListerWithEmbeddedTenant(tableName, tenantColumn, tableColumns),
    79  		embeddedTenantDeleter: repo.NewDeleterWithEmbeddedTenant(tableName, tenantColumn),
    80  		embeddedTenantGetter:  repo.NewSingleGetterWithEmbeddedTenant(tableName, tenantColumn, tableColumns),
    81  
    82  		creator:                        repo.NewCreator(tableName, tableColumns),
    83  		globalCreator:                  repo.NewCreatorGlobal(resource.Label, tableName, tableColumns),
    84  		updater:                        repo.NewUpdater(tableName, updatableColumns, idColumns),
    85  		versionedUpdater:               repo.NewUpdater(tableName, updatableColumns, versionedIDColumns),
    86  		updaterGlobal:                  repo.NewUpdaterGlobal(resource.Label, tableName, updatableColumns, idColumns),
    87  		embeddedTenantUpdater:          repo.NewUpdaterWithEmbeddedTenant(resource.Label, tableName, updatableColumns, tenantColumn, idColumns),
    88  		versionedEmbeddedTenantUpdater: repo.NewUpdaterWithEmbeddedTenant(resource.Label, tableName, updatableColumns, tenantColumn, versionedIDColumns),
    89  		conv:                           conv,
    90  	}
    91  }
    92  
    93  // Upsert missing godoc
    94  func (r *repository) Upsert(ctx context.Context, tenant string, label *model.Label) error {
    95  	if label == nil {
    96  		return apperrors.NewInternalError("item can not be empty")
    97  	}
    98  
    99  	l, err := r.GetByKey(ctx, tenant, label.ObjectType, label.ObjectID, label.Key)
   100  	if err != nil {
   101  		if apperrors.IsNotFoundError(err) {
   102  			return r.Create(ctx, tenant, label)
   103  		}
   104  		return err
   105  	}
   106  
   107  	l.Value = label.Value
   108  	labelEntity, err := r.conv.ToEntity(l)
   109  	if err != nil {
   110  		return errors.Wrap(err, "while creating label entity from model")
   111  	}
   112  	if label.ObjectType == model.TenantLabelableObject {
   113  		return r.embeddedTenantUpdater.UpdateSingleWithVersionGlobal(ctx, labelEntity)
   114  	}
   115  	if label.ObjectType == model.AppTemplateLabelableObject {
   116  		return r.updaterGlobal.UpdateSingleWithVersionGlobal(ctx, labelEntity)
   117  	}
   118  
   119  	return r.updater.UpdateSingleWithVersion(ctx, label.ObjectType.GetResourceType(), tenant, labelEntity)
   120  }
   121  
   122  // UpsertGlobal missing godoc
   123  func (r *repository) UpsertGlobal(ctx context.Context, label *model.Label) error {
   124  	if label == nil {
   125  		return apperrors.NewInternalError("item can not be empty")
   126  	}
   127  
   128  	l, err := r.GetByKeyGlobal(ctx, label.ObjectType, label.ObjectID, label.Key)
   129  	if err != nil {
   130  		if apperrors.IsNotFoundError(err) {
   131  			return r.CreateGlobal(ctx, label)
   132  		}
   133  		return err
   134  	}
   135  
   136  	l.Value = label.Value
   137  	labelEntity, err := r.conv.ToEntity(l)
   138  	if err != nil {
   139  		return errors.Wrap(err, "while creating label entity from model")
   140  	}
   141  
   142  	return r.updaterGlobal.UpdateSingleWithVersionGlobal(ctx, labelEntity)
   143  }
   144  
   145  // UpdateWithVersion missing godoc
   146  func (r *repository) UpdateWithVersion(ctx context.Context, tenant string, label *model.Label) error {
   147  	if label == nil {
   148  		return apperrors.NewInternalError("item can not be empty")
   149  	}
   150  	labelEntity, err := r.conv.ToEntity(label)
   151  	if err != nil {
   152  		return errors.Wrap(err, "while creating label entity from model")
   153  	}
   154  	if label.ObjectType == model.TenantLabelableObject {
   155  		return r.versionedEmbeddedTenantUpdater.UpdateSingleWithVersionGlobal(ctx, labelEntity)
   156  	}
   157  	return r.versionedUpdater.UpdateSingleWithVersion(ctx, label.ObjectType.GetResourceType(), tenant, labelEntity)
   158  }
   159  
   160  // Create missing godoc
   161  func (r *repository) Create(ctx context.Context, tenant string, label *model.Label) error {
   162  	if label == nil {
   163  		return apperrors.NewInternalError("item can not be empty")
   164  	}
   165  
   166  	labelEntity, err := r.conv.ToEntity(label)
   167  	if err != nil {
   168  		return errors.Wrap(err, "while creating label entity from model")
   169  	}
   170  
   171  	if label.ObjectType == model.TenantLabelableObject || label.ObjectType == model.AppTemplateLabelableObject {
   172  		return r.globalCreator.Create(ctx, labelEntity)
   173  	}
   174  
   175  	return r.creator.Create(ctx, label.ObjectType.GetResourceType(), tenant, labelEntity)
   176  }
   177  
   178  // CreateGlobal missing godoc
   179  func (r *repository) CreateGlobal(ctx context.Context, label *model.Label) error {
   180  	if label == nil {
   181  		return apperrors.NewInternalError("item can not be empty")
   182  	}
   183  
   184  	labelEntity, err := r.conv.ToEntity(label)
   185  	if err != nil {
   186  		return errors.Wrap(err, "while creating label entity from model")
   187  	}
   188  
   189  	return r.globalCreator.Create(ctx, labelEntity)
   190  }
   191  
   192  // GetByKey missing godoc
   193  func (r *repository) GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error) {
   194  	getter := r.getter
   195  	if objectType == model.TenantLabelableObject {
   196  		getter = r.embeddedTenantGetter
   197  	}
   198  
   199  	conds := repo.Conditions{repo.NewEqualCondition(keyColumn, key)}
   200  	if objectType != model.TenantLabelableObject {
   201  		conds = append(conds, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   202  	}
   203  
   204  	var entity Entity
   205  
   206  	if objectType == model.AppTemplateLabelableObject {
   207  		if err := r.getterGlobal.GetGlobal(ctx, conds, repo.NoOrderBy, &entity); err != nil {
   208  			return nil, err
   209  		}
   210  	} else {
   211  		if err := getter.Get(ctx, objectType.GetResourceType(), tenant, conds, repo.NoOrderBy, &entity); err != nil {
   212  			return nil, err
   213  		}
   214  	}
   215  
   216  	labelModel, err := r.conv.FromEntity(&entity)
   217  	if err != nil {
   218  		return nil, errors.Wrap(err, "while converting Label entity to model")
   219  	}
   220  
   221  	return labelModel, nil
   222  }
   223  
   224  // GetByKeyGlobal missing godoc
   225  func (r *repository) GetByKeyGlobal(ctx context.Context, objectType model.LabelableObject, objectID, key string) (*model.Label, error) {
   226  	conds := repo.Conditions{repo.NewEqualCondition(keyColumn, key)}
   227  	if objectType != model.TenantLabelableObject {
   228  		conds = append(conds, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   229  	}
   230  
   231  	var entity Entity
   232  
   233  	if err := r.getterGlobal.GetGlobal(ctx, conds, repo.NoOrderBy, &entity); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	labelModel, err := r.conv.FromEntity(&entity)
   238  	if err != nil {
   239  		return nil, errors.Wrap(err, "while converting Label entity to model")
   240  	}
   241  
   242  	return labelModel, nil
   243  }
   244  
   245  // ListForGlobalObject missing godoc
   246  func (r *repository) ListForGlobalObject(ctx context.Context, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) {
   247  	return r.ListForObject(ctx, "", objectType, objectID)
   248  }
   249  
   250  // ListForObject missing godoc
   251  func (r *repository) ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) {
   252  	var entities Collection
   253  
   254  	var conditions []repo.Condition
   255  
   256  	lister := r.lister
   257  	if objectType == model.TenantLabelableObject {
   258  		lister = r.embeddedTenantLister
   259  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.ApplicationLabelableObject)))
   260  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.RuntimeContextLabelableObject)))
   261  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.RuntimeLabelableObject)))
   262  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.AppTemplateLabelableObject)))
   263  	} else {
   264  		conditions = append(conditions, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   265  	}
   266  
   267  	if objectType == model.AppTemplateLabelableObject {
   268  		if err := r.listerGlobal.ListGlobal(ctx, &entities, conditions...); err != nil {
   269  			return nil, err
   270  		}
   271  	} else {
   272  		if err := lister.List(ctx, objectType.GetResourceType(), tenant, &entities, conditions...); err != nil {
   273  			return nil, err
   274  		}
   275  	}
   276  
   277  	labelsMap := make(map[string]*model.Label)
   278  
   279  	for _, entity := range entities {
   280  		m, err := r.conv.FromEntity(&entity)
   281  		if err != nil {
   282  			return nil, errors.Wrap(err, "while converting Label entity to model")
   283  		}
   284  
   285  		labelsMap[m.Key] = m
   286  	}
   287  
   288  	return labelsMap, nil
   289  }
   290  
   291  // ListForObjectIDs lists all labels for given object IDs
   292  func (r *repository) ListForObjectIDs(ctx context.Context, tenant string, objectType model.LabelableObject, objectIDs []string) (map[string]map[string]interface{}, error) {
   293  	if len(objectIDs) == 0 {
   294  		return nil, nil
   295  	}
   296  	var entities Collection
   297  
   298  	conditions := []repo.Condition{repo.NewInConditionForStringValues(labelableObjectField(objectType), objectIDs)}
   299  
   300  	lister := r.lister
   301  	if objectType == model.TenantLabelableObject {
   302  		lister = r.embeddedTenantLister
   303  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.ApplicationLabelableObject)))
   304  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.RuntimeContextLabelableObject)))
   305  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.RuntimeLabelableObject)))
   306  		conditions = append(conditions, repo.NewNullCondition(labelableObjectField(model.AppTemplateLabelableObject)))
   307  	}
   308  
   309  	if objectType == model.AppTemplateLabelableObject {
   310  		if err := r.listerGlobal.ListGlobal(ctx, &entities, conditions...); err != nil {
   311  			return nil, err
   312  		}
   313  	} else {
   314  		if err := lister.List(ctx, objectType.GetResourceType(), tenant, &entities, conditions...); err != nil {
   315  			return nil, err
   316  		}
   317  	}
   318  
   319  	labelsMap := make(map[string]map[string]interface{}, len(entities))
   320  
   321  	for _, entity := range entities {
   322  		m, err := r.conv.FromEntity(&entity)
   323  		if err != nil {
   324  			return nil, errors.Wrap(err, "while converting Label entity to model")
   325  		}
   326  
   327  		labelsForObject, ok := labelsMap[m.ObjectID]
   328  		if !ok {
   329  			labelsForObject = make(map[string]interface{})
   330  		}
   331  		labelsForObject[m.Key] = m.Value
   332  		labelsMap[m.ObjectID] = labelsForObject
   333  	}
   334  
   335  	return labelsMap, nil
   336  }
   337  
   338  // ListByKey missing godoc
   339  func (r *repository) ListByKey(ctx context.Context, tenant, key string) ([]*model.Label, error) {
   340  	var entities Collection
   341  	if err := r.lister.List(ctx, resource.Label, tenant, &entities, repo.NewEqualCondition(keyColumn, key)); err != nil {
   342  		return nil, err
   343  	}
   344  	return r.multipleFromEntity(entities)
   345  }
   346  
   347  // ListGlobalByKey lists all labels which are labeled with the provided key across tenants (global)
   348  func (r *repository) ListGlobalByKey(ctx context.Context, key string) ([]*model.Label, error) {
   349  	var entities Collection
   350  
   351  	if err := r.listerGlobal.ListGlobal(ctx, &entities, repo.NewEqualCondition(keyColumn, key)); err != nil {
   352  		return nil, err
   353  	}
   354  
   355  	return r.multipleFromEntity(entities)
   356  }
   357  
   358  // ListGlobalByKeyAndObjects lists resources of objectType across tenants (global) which match the given objectIDs and labeled with the provided key
   359  func (r *repository) ListGlobalByKeyAndObjects(ctx context.Context, objectType model.LabelableObject, objectIDs []string, key string) ([]*model.Label, error) {
   360  	var entities Collection
   361  	if err := r.listerGlobal.ListGlobalWithSelectForUpdate(ctx, &entities, repo.NewEqualCondition(keyColumn, key), repo.NewInConditionForStringValues(labelableObjectField(objectType), objectIDs)); err != nil {
   362  		return nil, err
   363  	}
   364  	return r.multipleFromEntity(entities)
   365  }
   366  
   367  // Delete missing godoc
   368  func (r *repository) Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error {
   369  	deleter := r.deleter
   370  	conds := repo.Conditions{repo.NewEqualCondition(keyColumn, key)}
   371  	if objectType == model.TenantLabelableObject {
   372  		deleter = r.embeddedTenantDeleter
   373  	} else {
   374  		conds = append(conds, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   375  	}
   376  	if objectType == model.AppTemplateLabelableObject {
   377  		return r.deleterGlobal.DeleteManyGlobal(ctx, conds)
   378  	}
   379  	return deleter.DeleteMany(ctx, objectType.GetResourceType(), tenant, conds)
   380  }
   381  
   382  // DeleteAll missing godoc
   383  func (r *repository) DeleteAll(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) error {
   384  	deleter := r.deleter
   385  	conds := repo.Conditions{}
   386  	if objectType == model.TenantLabelableObject {
   387  		deleter = r.embeddedTenantDeleter
   388  	} else {
   389  		conds = append(conds, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   390  	}
   391  	if objectType == model.AppTemplateLabelableObject {
   392  		return r.deleterGlobal.DeleteManyGlobal(ctx, conds)
   393  	}
   394  	return deleter.DeleteMany(ctx, objectType.GetResourceType(), tenant, conds)
   395  }
   396  
   397  // DeleteByKeyNegationPattern missing godoc
   398  func (r *repository) DeleteByKeyNegationPattern(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, labelKeyPattern string) error {
   399  	deleter := r.deleter
   400  	conds := repo.Conditions{repo.NewNotRegexConditionString(keyColumn, labelKeyPattern)}
   401  	if objectType == model.TenantLabelableObject {
   402  		deleter = r.embeddedTenantDeleter
   403  	} else {
   404  		conds = append(conds, repo.NewEqualCondition(labelableObjectField(objectType), objectID))
   405  	}
   406  	if objectType == model.AppTemplateLabelableObject {
   407  		return r.deleterGlobal.DeleteManyGlobal(ctx, conds)
   408  	}
   409  	return deleter.DeleteMany(ctx, objectType.GetResourceType(), tenant, conds)
   410  }
   411  
   412  // DeleteByKey missing godoc
   413  func (r *repository) DeleteByKey(ctx context.Context, tenant string, key string) error {
   414  	return r.deleter.DeleteMany(ctx, resource.Label, tenant, repo.Conditions{repo.NewEqualCondition(keyColumn, key)})
   415  }
   416  
   417  // GetScenarioLabelsForRuntimes missing godoc
   418  func (r *repository) GetScenarioLabelsForRuntimes(ctx context.Context, tenantID string, runtimesIDs []string) ([]model.Label, error) {
   419  	if len(runtimesIDs) == 0 {
   420  		return nil, apperrors.NewInvalidDataError("cannot execute query without runtimeIDs")
   421  	}
   422  
   423  	conditions := repo.Conditions{
   424  		repo.NewEqualCondition(keyColumn, model.ScenariosKey),
   425  		repo.NewInConditionForStringValues("runtime_id", runtimesIDs),
   426  	}
   427  
   428  	var labels Collection
   429  	err := r.lister.List(ctx, resource.RuntimeLabel, tenantID, &labels, conditions...)
   430  	if err != nil {
   431  		return nil, errors.Wrap(err, "while fetching runtimes scenarios")
   432  	}
   433  
   434  	labelModels := make([]model.Label, 0, len(labels))
   435  	for _, label := range labels {
   436  		labelModel, err := r.conv.FromEntity(&label)
   437  		if err != nil {
   438  			return nil, errors.Wrap(err, "while converting label entity to model")
   439  		}
   440  		labelModels = append(labelModels, *labelModel)
   441  	}
   442  	return labelModels, nil
   443  }
   444  
   445  func (r *repository) GetSubdomainLabelForSubscribedRuntime(ctx context.Context, tenantID string) (*model.Label, error) {
   446  	conds := repo.Conditions{
   447  		repo.NewEqualCondition(keyColumn, tenant.SubdomainLabelKey),
   448  		repo.NewInConditionForSubQuery(tenantColumn,
   449  			"SELECT DISTINCT tenant_id FROM tenant_runtime_contexts WHERE tenant_id=?", []interface{}{tenantID}),
   450  	}
   451  
   452  	var entity Entity
   453  	err := r.embeddedTenantGetter.Get(
   454  		ctx, model.TenantLabelableObject.GetResourceType(), tenantID, conds, repo.NoOrderBy, &entity)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	labelModel, err := r.conv.FromEntity(&entity)
   460  	if err != nil {
   461  		return nil, errors.Wrap(err, "while converting Label entity to model")
   462  	}
   463  
   464  	return labelModel, nil
   465  }
   466  
   467  func (r *repository) multipleFromEntity(entities []Entity) ([]*model.Label, error) {
   468  	labels := make([]*model.Label, 0, len(entities))
   469  
   470  	for _, entity := range entities {
   471  		m, err := r.conv.FromEntity(&entity)
   472  		if err != nil {
   473  			return nil, errors.Wrap(err, "while converting Label entity to model")
   474  		}
   475  
   476  		labels = append(labels, m)
   477  	}
   478  
   479  	return labels, nil
   480  }
   481  
   482  func labelableObjectField(objectType model.LabelableObject) string {
   483  	switch objectType {
   484  	case model.ApplicationLabelableObject:
   485  		return "app_id"
   486  	case model.RuntimeLabelableObject:
   487  		return "runtime_id"
   488  	case model.RuntimeContextLabelableObject:
   489  		return "runtime_context_id"
   490  	case model.TenantLabelableObject:
   491  		return "tenant_id"
   492  	case model.AppTemplateLabelableObject:
   493  		return "app_template_id"
   494  	}
   495  
   496  	return ""
   497  }