You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/host/repository_catalog.go

188 lines
6.2 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package host
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/plugin"
"github.com/hashicorp/boundary/internal/util"
)
// CatalogRepository coordinates calls across different subtype services
// to gather information about all host catalogs.
type CatalogRepository struct {
reader db.Reader
writer db.Writer
}
// NewCatalogRepository returns a new host catalog repository.
func NewCatalogRepository(ctx context.Context, reader db.Reader, writer db.Writer) (*CatalogRepository, error) {
const op = "host.NewCatalogRepository"
switch {
case util.IsNil(reader):
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing DB reader")
case util.IsNil(writer):
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing DB writer")
}
return &CatalogRepository{
reader: reader,
writer: writer,
}, nil
}
// List lists host catalogs across all subtypes.
func (s *CatalogRepository) List(ctx context.Context, projectIds []string, afterItem pagination.Item, limit int) ([]Catalog, []*plugin.Plugin, time.Time, error) {
const op = "host.(*CatalogRepository).list"
switch {
case len(projectIds) == 0:
return nil, nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing project ids")
case limit < 1:
return nil, nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing limit")
}
args := []any{sql.Named("project_ids", projectIds)}
whereClause := "project_id in @project_ids"
query := fmt.Sprintf(listCatalogsTemplate, whereClause, limit)
if afterItem != nil {
query = fmt.Sprintf(listCatalogsPageTemplate, whereClause, limit)
args = append(args,
sql.Named("last_item_create_time", afterItem.GetCreateTime()),
sql.Named("last_item_id", afterItem.GetPublicId()),
)
}
return s.queryCatalogs(ctx, query, args)
}
// ListRefresh lists host catalogs across all subtypes.
func (s *CatalogRepository) ListRefresh(ctx context.Context, projectIds []string, updatedAfter time.Time, afterItem pagination.Item, limit int) ([]Catalog, []*plugin.Plugin, time.Time, error) {
const op = "host.(*CatalogRepository).list"
switch {
case len(projectIds) == 0:
return nil, nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing project ids")
case updatedAfter.IsZero():
return nil, nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
case limit < 1:
return nil, nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing limit")
}
args := []any{sql.Named("project_ids", projectIds)}
whereClause := "project_id in @project_ids"
query := fmt.Sprintf(listCatalogsRefreshTemplate, whereClause, limit)
args = append(args,
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
)
if afterItem != nil {
query = fmt.Sprintf(listCatalogsRefreshPageTemplate, whereClause, limit)
args = append(args,
sql.Named("last_item_update_time", afterItem.GetUpdateTime()),
sql.Named("last_item_id", afterItem.GetPublicId()),
)
}
return s.queryCatalogs(ctx, query, args)
}
// EstimatedCount estimates the total number of host catalogs.
func (s *CatalogRepository) EstimatedCount(ctx context.Context) (int, error) {
const op = "host.(*CatalogRepository).EstimatedCount"
rows, err := s.reader.Query(ctx, estimateCountCatalogsQuery, nil)
if err != nil {
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total host catalogs"))
}
var count int
for rows.Next() {
if err := s.reader.ScanRows(ctx, rows, &count); err != nil {
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total host catalogs"))
}
}
if err := rows.Err(); err != nil {
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total host catalogs"))
}
return count, nil
}
// ListDeletedIds lists the deleted IDs of all host catalogs since the time specified.
func (s *CatalogRepository) ListDeletedIds(ctx context.Context, since time.Time) ([]string, time.Time, error) {
const op = "host.(*CatalogRepository).ListDeletedIds"
var deletedCatalogIDs []string
var transactionTimestamp time.Time
if _, err := s.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
rows, err := s.writer.Query(ctx, listDeletedIdsQuery, []any{sql.Named("since", since)})
if err != nil {
return errors.Wrap(ctx, err, op)
}
defer rows.Close()
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return errors.Wrap(ctx, err, op)
}
deletedCatalogIDs = append(deletedCatalogIDs, id)
}
if err := rows.Err(); err != nil {
return errors.Wrap(ctx, err, op)
}
transactionTimestamp, err = r.Now(ctx)
return err
}); err != nil {
return nil, time.Time{}, err
}
return deletedCatalogIDs, transactionTimestamp, nil
}
func (s *CatalogRepository) queryCatalogs(ctx context.Context, query string, args []any) ([]Catalog, []*plugin.Plugin, time.Time, error) {
const op = "host.(*CatalogRepository).queryCatalogs"
var catalogs []Catalog
var plugins []*plugin.Plugin
var transactionTimestamp time.Time
if _, err := s.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, w db.Writer) error {
rows, err := r.Query(ctx, query, args)
if err != nil {
return err
}
defer rows.Close()
var foundCatalogs []*CatalogListQueryResult
for rows.Next() {
if err := r.ScanRows(ctx, rows, &foundCatalogs); err != nil {
return err
}
}
if err := rows.Err(); err != nil {
return err
}
var plgIds []string
for _, c := range foundCatalogs {
catalog, err := c.toCatalog(ctx)
if err != nil {
return err
}
catalogs = append(catalogs, catalog)
if c.PluginId != "" {
plgIds = append(plgIds, c.PluginId)
}
}
if err := r.SearchWhere(ctx, &plugins, "public_id in (?)", []any{plgIds}); err != nil {
return errors.Wrap(ctx, err, op)
}
transactionTimestamp, err = r.Now(ctx)
return err
}); err != nil {
return nil, nil, time.Time{}, errors.Wrap(ctx, err, op)
}
return catalogs, plugins, transactionTimestamp, nil
}