* fix: use the db transaction reader and writer * fix: internal/server: use ctx when scanning rows * fix(host): pass db tx reader/writer into helper funcs * fix: more precise nil check against db.Reader & db.Writer --------- Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
188 lines
6.2 KiB
Go
188 lines
6.2 KiB
Go
// 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 := w.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
|
|
}
|