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

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
     8  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
     9  
    10  	"github.com/google/uuid"
    11  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    12  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    16  	"github.com/kyma-incubator/compass/components/director/internal/model"
    17  )
    18  
    19  const runtimeTable string = `public.runtimes`
    20  
    21  var (
    22  	runtimeColumns   = []string{"id", "name", "description", "status_condition", "status_timestamp", "creation_timestamp", "application_namespace"}
    23  	updatableColumns = []string{"name", "description", "status_condition", "status_timestamp", "application_namespace"}
    24  )
    25  
    26  // EntityConverter missing godoc
    27  //go:generate mockery --name=EntityConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    28  type EntityConverter interface {
    29  	ToEntity(in *model.Runtime) (*Runtime, error)
    30  	FromEntity(entity *Runtime) *model.Runtime
    31  }
    32  
    33  type pgRepository struct {
    34  	existQuerier       repo.ExistQuerier
    35  	ownerExistQuerier  repo.ExistQuerier
    36  	singleGetter       repo.SingleGetter
    37  	singleGetterGlobal repo.SingleGetterGlobal
    38  	deleter            repo.Deleter
    39  	pageableQuerier    repo.PageableQuerier
    40  	lister             repo.Lister
    41  	ownerLister        repo.Lister
    42  	listerGlobal       repo.ListerGlobal
    43  	creator            repo.Creator
    44  	updater            repo.Updater
    45  	conv               EntityConverter
    46  }
    47  
    48  // NewRepository missing godoc
    49  func NewRepository(conv EntityConverter) *pgRepository {
    50  	return &pgRepository{
    51  		existQuerier:       repo.NewExistQuerier(runtimeTable),
    52  		ownerExistQuerier:  repo.NewExistQuerierWithOwnerCheck(runtimeTable),
    53  		singleGetter:       repo.NewSingleGetter(runtimeTable, runtimeColumns),
    54  		singleGetterGlobal: repo.NewSingleGetterGlobal(resource.Runtime, runtimeTable, runtimeColumns),
    55  		deleter:            repo.NewDeleter(runtimeTable),
    56  		pageableQuerier:    repo.NewPageableQuerier(runtimeTable, runtimeColumns),
    57  		lister:             repo.NewLister(runtimeTable, runtimeColumns),
    58  		ownerLister:        repo.NewOwnerLister(runtimeTable, runtimeColumns, true),
    59  		listerGlobal:       repo.NewListerGlobal(resource.Runtime, runtimeTable, runtimeColumns),
    60  		creator:            repo.NewCreator(runtimeTable, runtimeColumns),
    61  		updater:            repo.NewUpdater(runtimeTable, updatableColumns, []string{"id"}),
    62  		conv:               conv,
    63  	}
    64  }
    65  
    66  // Exists missing godoc
    67  func (r *pgRepository) Exists(ctx context.Context, tenant, id string) (bool, error) {
    68  	return r.existQuerier.Exists(ctx, resource.Runtime, tenant, repo.Conditions{repo.NewEqualCondition("id", id)})
    69  }
    70  
    71  // OwnerExists checks if runtime with given id and tenant exists and has owner access
    72  func (r *pgRepository) OwnerExists(ctx context.Context, tenant, id string) (bool, error) {
    73  	return r.ownerExistQuerier.Exists(ctx, resource.Runtime, tenant, repo.Conditions{repo.NewEqualCondition("id", id)})
    74  }
    75  
    76  // OwnerExistsByFiltersAndID checks if runtime with given id and filters in given tenant exists and has owner access. The results from the filter subqueries are combined using UNION
    77  func (r *pgRepository) OwnerExistsByFiltersAndID(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (bool, error) {
    78  	tenantID, err := uuid.Parse(tenant)
    79  	if err != nil {
    80  		return false, errors.Wrap(err, "while parsing tenant as UUID")
    81  	}
    82  
    83  	additionalConditions := repo.Conditions{repo.NewEqualCondition("id", id)}
    84  
    85  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.UnionSet, tenantID, filter)
    86  	if err != nil {
    87  		return false, errors.Wrap(err, "while building filter query")
    88  	}
    89  	if filterSubquery != "" {
    90  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
    91  	}
    92  
    93  	return r.ownerExistQuerier.Exists(ctx, resource.Runtime, tenant, additionalConditions)
    94  }
    95  
    96  // Delete missing godoc
    97  func (r *pgRepository) Delete(ctx context.Context, tenant string, id string) error {
    98  	return r.deleter.DeleteOne(ctx, resource.Runtime, tenant, repo.Conditions{repo.NewEqualCondition("id", id)})
    99  }
   100  
   101  // GetByID missing godoc
   102  func (r *pgRepository) GetByID(ctx context.Context, tenant, id string) (*model.Runtime, error) {
   103  	var runtimeEnt Runtime
   104  	if err := r.singleGetter.Get(ctx, resource.Runtime, tenant, repo.Conditions{repo.NewEqualCondition("id", id)}, repo.NoOrderBy, &runtimeEnt); err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	runtimeModel := r.conv.FromEntity(&runtimeEnt)
   109  
   110  	return runtimeModel, nil
   111  }
   112  
   113  // GetByFiltersAndID missing godoc
   114  func (r *pgRepository) GetByFiltersAndID(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) {
   115  	tenantID, err := uuid.Parse(tenant)
   116  	if err != nil {
   117  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   118  	}
   119  
   120  	additionalConditions := repo.Conditions{repo.NewEqualCondition("id", id)}
   121  
   122  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.IntersectSet, tenantID, filter)
   123  	if err != nil {
   124  		return nil, errors.Wrap(err, "while building filter query")
   125  	}
   126  	if filterSubquery != "" {
   127  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   128  	}
   129  
   130  	var runtimeEnt Runtime
   131  	if err := r.singleGetter.Get(ctx, resource.Runtime, tenant, additionalConditions, repo.NoOrderBy, &runtimeEnt); err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	runtimeModel := r.conv.FromEntity(&runtimeEnt)
   136  
   137  	return runtimeModel, nil
   138  }
   139  
   140  // GetByFiltersAndIDUsingUnion retrieves runtime by its ID if it matches the provided filters. The results from the filter subqueries are combined sing UNION
   141  func (r *pgRepository) GetByFiltersAndIDUsingUnion(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) {
   142  	tenantID, err := uuid.Parse(tenant)
   143  	if err != nil {
   144  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   145  	}
   146  
   147  	additionalConditions := repo.Conditions{repo.NewEqualCondition("id", id)}
   148  
   149  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.UnionSet, tenantID, filter)
   150  	if err != nil {
   151  		return nil, errors.Wrap(err, "while building filter query")
   152  	}
   153  	if filterSubquery != "" {
   154  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   155  	}
   156  
   157  	var runtimeEnt Runtime
   158  	if err := r.singleGetter.Get(ctx, resource.Runtime, tenant, additionalConditions, repo.NoOrderBy, &runtimeEnt); err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	runtimeModel := r.conv.FromEntity(&runtimeEnt)
   163  
   164  	return runtimeModel, nil
   165  }
   166  
   167  // GetByFilters retrieves model.Runtime matching on the given label filters
   168  func (r *pgRepository) GetByFilters(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) {
   169  	var runtimeEnt Runtime
   170  
   171  	tenantID, err := uuid.Parse(tenant)
   172  	if err != nil {
   173  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   174  	}
   175  
   176  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.IntersectSet, tenantID, filter)
   177  	if err != nil {
   178  		return nil, errors.Wrap(err, "while building filter query")
   179  	}
   180  
   181  	var conditions repo.Conditions
   182  	if filterSubquery != "" {
   183  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   184  	}
   185  
   186  	if err = r.singleGetter.Get(ctx, resource.Runtime, tenant, conditions, repo.NoOrderBy, &runtimeEnt); err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	appModel := r.conv.FromEntity(&runtimeEnt)
   191  
   192  	return appModel, nil
   193  }
   194  
   195  // GetByFiltersGlobal missing godoc
   196  func (r *pgRepository) GetByFiltersGlobal(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.Runtime, error) {
   197  	filterSubquery, args, err := label.FilterQueryGlobal(model.RuntimeLabelableObject, label.IntersectSet, filter)
   198  	if err != nil {
   199  		return nil, errors.Wrap(err, "while building filter query")
   200  	}
   201  
   202  	var additionalConditions repo.Conditions
   203  	if filterSubquery != "" {
   204  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   205  	}
   206  
   207  	var runtimeEnt Runtime
   208  	if err := r.singleGetterGlobal.GetGlobal(ctx, additionalConditions, repo.NoOrderBy, &runtimeEnt); err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	runtimeModel := r.conv.FromEntity(&runtimeEnt)
   213  
   214  	return runtimeModel, nil
   215  }
   216  
   217  // ListByFiltersGlobal missing godoc
   218  func (r *pgRepository) ListByFiltersGlobal(ctx context.Context, filters []*labelfilter.LabelFilter) ([]*model.Runtime, error) {
   219  	filterSubquery, args, err := label.FilterQueryGlobal(model.RuntimeLabelableObject, label.IntersectSet, filters)
   220  	if err != nil {
   221  		return nil, errors.Wrap(err, "while building filter query")
   222  	}
   223  
   224  	var additionalConditions repo.Conditions
   225  	if filterSubquery != "" {
   226  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   227  	}
   228  
   229  	var entities RuntimeCollection
   230  	if err := r.listerGlobal.ListGlobal(ctx, &entities, additionalConditions...); err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	return r.multipleFromEntities(entities), nil
   235  }
   236  
   237  // ListAll returns all runtimes in a tenant that match given label filter and owner check to false. The results from the separate filters are combined using 'INTERSECT'.
   238  func (r *pgRepository) ListAll(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) {
   239  	return r.listRuntimes(ctx, tenant, filter, label.IntersectSet, false)
   240  }
   241  
   242  // ListAllWithUnionSetCombination returns all runtimes in a tenant that match given label filter and owner check to false. The results from the separate filters are combined using 'UNION'.
   243  func (r *pgRepository) ListAllWithUnionSetCombination(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) {
   244  	return r.listRuntimes(ctx, tenant, filter, label.UnionSet, false)
   245  }
   246  
   247  // ListOwnedRuntimes returns all runtimes in a tenant that match given label filter and owner check to true, The results from the separate filters are combined using 'UNION'.
   248  func (r *pgRepository) ListOwnedRuntimes(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) {
   249  	return r.listRuntimes(ctx, tenant, filter, label.UnionSet, true)
   250  }
   251  
   252  // RuntimeCollection missing godoc
   253  type RuntimeCollection []Runtime
   254  
   255  // Len missing godoc
   256  func (r RuntimeCollection) Len() int {
   257  	return len(r)
   258  }
   259  
   260  // List missing godoc
   261  func (r *pgRepository) List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error) {
   262  	var runtimesCollection RuntimeCollection
   263  	tenantID, err := uuid.Parse(tenant)
   264  	if err != nil {
   265  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   266  	}
   267  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.IntersectSet, tenantID, filter)
   268  	if err != nil {
   269  		return nil, errors.Wrap(err, "while building filter query")
   270  	}
   271  
   272  	var conditions repo.Conditions
   273  	if filterSubquery != "" {
   274  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   275  	}
   276  
   277  	page, totalCount, err := r.pageableQuerier.List(ctx, resource.Runtime, tenant, pageSize, cursor, "name", &runtimesCollection, conditions...)
   278  
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	items := r.multipleFromEntities(runtimesCollection)
   284  
   285  	return &model.RuntimePage{
   286  		Data:       items,
   287  		TotalCount: totalCount,
   288  		PageInfo:   page}, nil
   289  }
   290  
   291  // Create missing godoc
   292  func (r *pgRepository) Create(ctx context.Context, tenant string, item *model.Runtime) error {
   293  	if item == nil {
   294  		return apperrors.NewInternalError("item can not be empty")
   295  	}
   296  
   297  	runtimeEnt, err := r.conv.ToEntity(item)
   298  	if err != nil {
   299  		return errors.Wrap(err, "while creating runtime entity from model")
   300  	}
   301  
   302  	return r.creator.Create(ctx, resource.Runtime, tenant, runtimeEnt)
   303  }
   304  
   305  // Update missing godoc
   306  func (r *pgRepository) Update(ctx context.Context, tenant string, item *model.Runtime) error {
   307  	if item == nil {
   308  		return apperrors.NewInternalError("item cannot be nil")
   309  	}
   310  	runtimeEnt, err := r.conv.ToEntity(item)
   311  	if err != nil {
   312  		return errors.Wrap(err, "while creating runtime entity from model")
   313  	}
   314  	return r.updater.UpdateSingle(ctx, resource.Runtime, tenant, runtimeEnt)
   315  }
   316  
   317  // GetOldestForFilters missing godoc
   318  func (r *pgRepository) GetOldestForFilters(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) {
   319  	tenantID, err := uuid.Parse(tenant)
   320  	if err != nil {
   321  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   322  	}
   323  
   324  	var additionalConditions repo.Conditions
   325  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, label.IntersectSet, tenantID, filter)
   326  	if err != nil {
   327  		return nil, errors.Wrap(err, "while building filter query")
   328  	}
   329  	if filterSubquery != "" {
   330  		additionalConditions = append(additionalConditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   331  	}
   332  
   333  	orderByParams := repo.OrderByParams{repo.NewAscOrderBy("creation_timestamp")}
   334  
   335  	var runtimeEnt Runtime
   336  	if err := r.singleGetter.Get(ctx, resource.Runtime, tenant, additionalConditions, orderByParams, &runtimeEnt); err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	runtimeModel := r.conv.FromEntity(&runtimeEnt)
   341  
   342  	return runtimeModel, nil
   343  }
   344  
   345  // ListByScenariosAndIDs lists all runtimes with given IDs that are in any of the given scenarios
   346  func (r *pgRepository) ListByScenariosAndIDs(ctx context.Context, tenant string, scenarios []string, ids []string) ([]*model.Runtime, error) {
   347  	if len(scenarios) == 0 || len(ids) == 0 {
   348  		return nil, nil
   349  	}
   350  	tenantUUID, err := uuid.Parse(tenant)
   351  	if err != nil {
   352  		return nil, apperrors.NewInvalidDataError("tenantID is not UUID")
   353  	}
   354  
   355  	var entities RuntimeCollection
   356  
   357  	// Scenarios query part
   358  	scenariosFilters := make([]*labelfilter.LabelFilter, 0, len(scenarios))
   359  	for _, scenarioValue := range scenarios {
   360  		query := fmt.Sprintf(`$[*] ? (@ == "%s")`, scenarioValue)
   361  		scenariosFilters = append(scenariosFilters, labelfilter.NewForKeyWithQuery(model.ScenariosKey, query))
   362  	}
   363  
   364  	scenariosSubquery, scenariosArgs, err := label.FilterQuery(model.RuntimeLabelableObject, label.UnionSet, tenantUUID, scenariosFilters)
   365  	if err != nil {
   366  		return nil, errors.Wrap(err, "while creating scenarios filter query")
   367  	}
   368  
   369  	var conditions repo.Conditions
   370  	if scenariosSubquery != "" {
   371  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", scenariosSubquery, scenariosArgs))
   372  	}
   373  
   374  	conditions = append(conditions, repo.NewInConditionForStringValues("id", ids))
   375  
   376  	if err := r.lister.List(ctx, resource.Runtime, tenant, &entities, conditions...); err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	return r.multipleFromEntities(entities), nil
   381  }
   382  
   383  // ListByScenarios lists all runtimes with given IDs that are in any of the given scenarios
   384  func (r *pgRepository) ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.Runtime, error) {
   385  	if len(scenarios) == 0 {
   386  		return nil, nil
   387  	}
   388  	tenantUUID, err := uuid.Parse(tenant)
   389  	if err != nil {
   390  		return nil, apperrors.NewInvalidDataError("tenantID is not UUID")
   391  	}
   392  
   393  	var entities RuntimeCollection
   394  
   395  	// Scenarios query part
   396  	scenariosFilters := make([]*labelfilter.LabelFilter, 0, len(scenarios))
   397  	for _, scenarioValue := range scenarios {
   398  		query := fmt.Sprintf(`$[*] ? (@ == "%s")`, scenarioValue)
   399  		scenariosFilters = append(scenariosFilters, labelfilter.NewForKeyWithQuery(model.ScenariosKey, query))
   400  	}
   401  
   402  	scenariosSubquery, scenariosArgs, err := label.FilterQuery(model.RuntimeLabelableObject, label.UnionSet, tenantUUID, scenariosFilters)
   403  	if err != nil {
   404  		return nil, errors.Wrap(err, "while creating scenarios filter query")
   405  	}
   406  
   407  	var conditions repo.Conditions
   408  	if scenariosSubquery != "" {
   409  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", scenariosSubquery, scenariosArgs))
   410  	}
   411  
   412  	if err := r.lister.List(ctx, resource.Runtime, tenant, &entities, conditions...); err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	return r.multipleFromEntities(entities), nil
   417  }
   418  
   419  // ListByIDs lists all runtimes with given IDs
   420  func (r *pgRepository) ListByIDs(ctx context.Context, tenant string, ids []string) ([]*model.Runtime, error) {
   421  	if len(ids) == 0 {
   422  		return nil, nil
   423  	}
   424  	var entities RuntimeCollection
   425  
   426  	if err := r.lister.List(ctx, resource.Runtime, tenant, &entities, repo.NewInConditionForStringValues("id", ids)); err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	return r.multipleFromEntities(entities), nil
   431  }
   432  
   433  func (r *pgRepository) listRuntimes(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, setCombination label.SetCombination, ownerCheck bool) ([]*model.Runtime, error) {
   434  	var entities RuntimeCollection
   435  
   436  	tenantID, err := uuid.Parse(tenant)
   437  	if err != nil {
   438  		return nil, errors.Wrap(err, "while parsing tenant as UUID")
   439  	}
   440  
   441  	filterSubquery, args, err := label.FilterQuery(model.RuntimeLabelableObject, setCombination, tenantID, filter)
   442  	if err != nil {
   443  		return nil, errors.Wrap(err, "while building filter query")
   444  	}
   445  
   446  	var conditions repo.Conditions
   447  	if filterSubquery != "" {
   448  		conditions = append(conditions, repo.NewInConditionForSubQuery("id", filterSubquery, args))
   449  	}
   450  
   451  	if ownerCheck {
   452  		if err = r.ownerLister.List(ctx, resource.Runtime, tenant, &entities, conditions...); err != nil {
   453  			return nil, err
   454  		}
   455  	} else {
   456  		if err = r.lister.List(ctx, resource.Runtime, tenant, &entities, conditions...); err != nil {
   457  			return nil, err
   458  		}
   459  	}
   460  
   461  	return r.multipleFromEntities(entities), nil
   462  }
   463  
   464  func (r *pgRepository) multipleFromEntities(entities RuntimeCollection) []*model.Runtime {
   465  	items := make([]*model.Runtime, 0, len(entities))
   466  	for _, ent := range entities {
   467  		model := r.conv.FromEntity(&ent)
   468  
   469  		items = append(items, model)
   470  	}
   471  	return items
   472  }