2022-07-20 16:24:46 -07:00
// Copyright (c) HashiCorp, Inc.
2021-11-10 17:08:42 -08:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2021-09-09 10:45:41 -07:00
package plugin
import (
"context"
2023-10-30 14:43:12 -07:00
"database/sql"
2021-09-09 10:45:41 -07:00
"fmt"
2021-11-10 17:08:42 -08:00
"strings"
2021-11-01 09:07:56 -07:00
"time"
2021-09-09 10:45:41 -07:00
"github.com/hashicorp/boundary/internal/db"
2021-11-01 09:07:56 -07:00
"github.com/hashicorp/boundary/internal/db/timestamp"
2021-09-09 10:45:41 -07:00
"github.com/hashicorp/boundary/internal/errors"
2021-09-20 12:48:38 -04:00
"github.com/hashicorp/boundary/internal/host"
2021-09-09 10:45:41 -07:00
"github.com/hashicorp/boundary/internal/kms"
2021-09-26 08:17:21 -07:00
"github.com/hashicorp/boundary/internal/libs/endpoint"
2021-11-09 14:42:48 -07:00
"github.com/hashicorp/boundary/internal/libs/patchstruct"
2021-09-09 10:45:41 -07:00
"github.com/hashicorp/boundary/internal/oplog"
2023-04-24 19:27:41 -04:00
"github.com/hashicorp/boundary/internal/plugin"
2022-01-11 15:55:26 -08:00
"github.com/hashicorp/boundary/internal/scheduler"
2022-08-18 15:39:30 -04:00
"github.com/hashicorp/boundary/internal/util"
2024-08-21 18:19:34 +01:00
hcpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
2021-09-21 09:18:49 -07:00
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
2021-11-10 17:08:42 -08:00
"google.golang.org/protobuf/types/known/wrapperspb"
2021-09-09 10:45:41 -07:00
)
2022-08-18 15:39:30 -04:00
// normalizeCatalogAttributes allows a plugin to normalize set attributes before
// they are saved
2024-08-21 18:19:34 +01:00
func normalizeSetAttributes ( ctx context . Context , plgClient plgpb . HostPluginServiceClient , plgHc * hcpb . HostCatalog , plgHs * pb . HostSet ) error {
2022-08-18 15:39:30 -04:00
const op = "plugin.(Repository).normalizeSetAttributes"
switch {
case util . IsNil ( plgClient ):
return errors . New ( ctx , errors . InvalidParameter , op , "plugin client is nil" )
case plgHs == nil :
return errors . New ( ctx , errors . InvalidParameter , op , "host set is nil" )
2024-08-21 18:19:34 +01:00
case plgHc . GetWorkerFilter (). GetValue () != "" && plgHc . GetPlugin () == nil :
return errors . New ( ctx , errors . InvalidParameter , op , "plugin data is not available on host catalog with worker filter" )
2022-08-18 15:39:30 -04:00
case plgHs . GetAttributes () == nil :
return nil
}
ret , err := plgClient . NormalizeSetData ( ctx , & plgpb . NormalizeSetDataRequest {
Attributes : plgHs . GetAttributes (),
2024-08-21 18:19:34 +01:00
Plugin : plgHc . GetPlugin (),
2022-08-18 15:39:30 -04:00
})
switch {
case err == nil :
if ret . Attributes != nil {
plgHs . Attrs = & pb . HostSet_Attributes {
Attributes : ret . Attributes ,
}
}
case status . Code ( err ) == codes . Unimplemented :
// Do nothing
default :
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "error asking plugin to normalize set data" ))
}
return nil
}
2021-09-09 10:45:41 -07:00
// CreateSet inserts s into the repository and returns a new HostSet
// containing the host set's PublicId. s is not changed. s must contain a
// valid CatalogId. s must not contain a PublicId. The PublicId is
// generated and assigned by this method. opt is ignored.
//
// Both s.Name and s.Description are optional. If s.Name is set, it must be
// unique within s.CatalogId.
2023-04-24 19:27:41 -04:00
func ( r * Repository ) CreateSet ( ctx context . Context , projectId string , s * HostSet , _ ... Option ) ( * HostSet , * plugin . Plugin , error ) {
2021-09-09 10:45:41 -07:00
const op = "plugin.(Repository).CreateSet"
if s == nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "nil HostSet" )
2021-09-09 10:45:41 -07:00
}
if s . HostSet == nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "nil embedded HostSet" )
2021-09-09 10:45:41 -07:00
}
if s . CatalogId == "" {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "no catalog id" )
2021-09-09 10:45:41 -07:00
}
if s . PublicId != "" {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "public id not empty" )
2021-09-09 10:45:41 -07:00
}
2022-07-20 16:24:46 -07:00
if projectId == "" {
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "no project id" )
2021-09-09 10:45:41 -07:00
}
2021-11-11 11:28:08 -05:00
if s . SyncIntervalSeconds < - 1 {
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "invalid sync interval" )
}
2021-09-09 10:45:41 -07:00
if s . Attributes == nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "nil attributes" )
2021-09-09 10:45:41 -07:00
}
s = s . clone ()
2021-11-09 14:42:48 -07:00
// Use PatchBytes' functionality that does not add keys where the values
// are nil to the resulting struct since we do not want to store nil valued
// attributes.
var err error
s . Attributes , err = patchstruct . PatchBytes ([] byte {}, s . Attributes )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2021-10-28 12:46:42 -07:00
c , per , err := r . getCatalog ( ctx , s . CatalogId )
2021-09-21 09:18:49 -07:00
if err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "looking up catalog" ))
2021-09-21 09:18:49 -07:00
}
2021-09-24 13:16:13 -07:00
id , err := newHostSetId ( ctx )
2021-09-09 10:45:41 -07:00
if err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-09 10:45:41 -07:00
}
s . PublicId = id
2021-11-01 09:07:56 -07:00
s . LastSyncTime = timestamp . New ( time . Unix ( 0 , 0 ))
s . NeedSync = true
2021-09-09 10:45:41 -07:00
2024-08-21 18:19:34 +01:00
plg , err := r . getPlugin ( ctx , c . GetPluginId ())
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
plgHc , err := toPluginCatalog ( ctx , c , plg )
2021-09-21 09:18:49 -07:00
if err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-21 09:18:49 -07:00
}
plgHs , err := toPluginSet ( ctx , s )
if err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-21 09:18:49 -07:00
}
2024-09-04 15:05:47 +01:00
plgClient , err := pluginClientFactoryFn ( ctx , plgHc , r . plugins )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2022-08-18 15:39:30 -04:00
if plgHs . GetAttributes () != nil {
2024-08-21 18:19:34 +01:00
if err := normalizeSetAttributes ( ctx , plgClient , plgHc , plgHs ); err != nil {
2022-08-18 15:39:30 -04:00
return nil , nil , errors . Wrap ( ctx , err , op )
}
2023-06-20 13:38:52 -04:00
if s . Attributes , err = proto . Marshal ( plgHs . GetAttributes ()); err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2022-08-18 15:39:30 -04:00
}
2024-08-01 11:18:53 -04:00
var preferredEndpoints [] * host . PreferredEndpoint
2021-09-20 12:48:38 -04:00
if s . PreferredEndpoints != nil {
2024-08-01 11:18:53 -04:00
preferredEndpoints = make ([] * host . PreferredEndpoint , 0 , len ( s . PreferredEndpoints ))
2021-09-20 12:48:38 -04:00
for i , e := range s . PreferredEndpoints {
obj , err := host . NewPreferredEndpoint ( ctx , s . PublicId , uint32 ( i + 1 ), e )
if err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-20 12:48:38 -04:00
}
preferredEndpoints = append ( preferredEndpoints , obj )
}
}
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , projectId , kms . KeyPurposeOplog )
2021-10-20 12:20:28 -07:00
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
}
2021-11-10 09:04:29 -07:00
var calledPluginSuccessfully bool
2021-09-20 12:48:38 -04:00
var returnedHostSet * HostSet
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
2021-09-09 10:45:41 -07:00
func ( _ db . Reader , w db . Writer ) error {
2021-09-20 12:48:38 -04:00
msgs := make ([] * oplog . Message , 0 , len ( preferredEndpoints ) + 2 )
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , s )
2021-09-09 10:45:41 -07:00
if err != nil {
2021-09-20 12:48:38 -04:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
}
returnedHostSet = s . clone ()
var hsOplogMsg oplog . Message
if err := w . Create ( ctx , returnedHostSet , db . NewOplogMsg ( & hsOplogMsg )); err != nil {
2021-09-09 10:45:41 -07:00
return errors . Wrap ( ctx , err , op )
}
2021-09-20 12:48:38 -04:00
msgs = append ( msgs , & hsOplogMsg )
if len ( preferredEndpoints ) > 0 {
peOplogMsgs := make ([] * oplog . Message , 0 , len ( preferredEndpoints ))
if err := w . CreateItems ( ctx , preferredEndpoints , db . NewOplogMsgs ( & peOplogMsgs )); err != nil {
return err
}
msgs = append ( msgs , peOplogMsgs ... )
}
2021-11-10 09:04:29 -07:00
if ! calledPluginSuccessfully {
if _ , err := plgClient . OnCreateSet ( ctx , & plgpb . OnCreateSetRequest { Catalog : plgHc , Set : plgHs , Persisted : per }); err != nil {
if status . Code ( err ) != codes . Unimplemented {
return errors . Wrap ( ctx , err , op )
}
}
calledPluginSuccessfully = true
}
2021-09-20 12:48:38 -04:00
metadata := s . oplog ( oplog . OpType_OP_TYPE_CREATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
}
2021-09-09 10:45:41 -07:00
return nil
},
)
if err != nil {
if errors . IsUniqueError ( err ) {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in catalog: %s: name %s already exists" , s . CatalogId , s . Name )))
2021-09-09 10:45:41 -07:00
}
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in catalog: %s" , s . CatalogId )))
2021-09-09 10:45:41 -07:00
}
2021-11-01 09:07:56 -07:00
// The set now exists in the plugin, sync it immediately.
2022-01-11 15:55:26 -08:00
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , setSyncJobName , 0 , scheduler . WithRunNow ( true ))
2021-11-01 09:07:56 -07:00
2021-09-24 13:16:13 -07:00
return returnedHostSet , plg , nil
2021-09-09 10:45:41 -07:00
}
2021-11-10 17:08:42 -08:00
// UpdateSet updates the repository for host set entry s with the
// values populated, for the fields listed in fieldMask. It returns a
// new HostSet containing the updated values, the hosts in the set,
// and a count of the number of records updated. s is not changed.
//
// s must contain a valid PublicId and CatalogId. Name, Description,
// Attributes, and PreferredEndpoints can be updated. Name must be
// unique among all sets associated with a single catalog.
//
// An attribute of s will be set to NULL in the database if the
// attribute in s is the zero value and it is included in fieldMask.
// Note that this does not apply to s.Attributes - a null
// s.Attributes is a no-op for modifications. Rather, if fields need
// to be reset, its field in c.Attributes should individually set to
// null.
//
// Updates are sent to OnUpdateSet with a full copy of both the
// current set, the state of the new set should it be updated, along
// with its parent host catalog and persisted state (can include
// secrets). This is a stateless call and does not affect the final
// record written, but some plugins may perform some actions on this
// call. Update of the record in the database is aborted if this call
// fails.
2023-04-24 19:27:41 -04:00
func ( r * Repository ) UpdateSet ( ctx context . Context , projectId string , s * HostSet , version uint32 , fieldMask [] string , opt ... Option ) ( * HostSet , [] * Host , * plugin . Plugin , int , error ) {
2021-11-10 17:08:42 -08:00
const op = "plugin.(Repository).UpdateSet"
if s == nil {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "nil HostSet" )
}
if s . HostSet == nil {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "nil embedded HostSet" )
}
if s . PublicId == "" {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no public id" )
}
2022-07-20 16:24:46 -07:00
if projectId == "" {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no project id" )
2021-11-10 17:08:42 -08:00
}
if len ( fieldMask ) == 0 {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . EmptyFieldMask , op , "empty field mask" )
}
// Get the old set first. We patch the record first before
// sending it to the DB for updating so that we can run on
// OnUpdateSet. Note that the field masks are still used for
// updating.
sets , plg , err := r . getSets ( ctx , s . PublicId , "" )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
if len ( sets ) == 0 {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . RecordNotFound , op , fmt . Sprintf ( "host set id %q not found" , s . PublicId ))
}
if len ( sets ) != 1 {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . Internal , op , fmt . Sprintf ( "unexpected amount of sets found, want=1, got=%d" , len ( sets )))
}
currentSet := sets [ 0 ]
// Assert the version of the current set to make sure we're
// updating the correct one.
if currentSet . GetVersion () != version {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . VersionMismatch , op , fmt . Sprintf ( "set version mismatch, want=%d, got=%d" , currentSet . GetVersion (), version ))
}
// Clone the set so that we can set fields.
newSet := currentSet . clone ()
var updateAttributes bool
2021-11-12 14:55:22 -07:00
var updateSyncInterval bool
2021-11-10 17:08:42 -08:00
var dbMask , nullFields [] string
const (
endpointOpNoop = "endpointOpNoop"
endpointOpDelete = "endpointOpDelete"
endpointOpUpdate = "endpointOpUpdate"
)
var endpointOp string = endpointOpNoop
for _ , f := range fieldMask {
switch {
case strings . EqualFold ( "name" , f ) && s . Name == "" :
nullFields = append ( nullFields , "name" )
newSet . Name = s . Name
case strings . EqualFold ( "name" , f ) && s . Name != "" :
dbMask = append ( dbMask , "name" )
newSet . Name = s . Name
case strings . EqualFold ( "description" , f ) && s . Description == "" :
nullFields = append ( nullFields , "description" )
newSet . Description = s . Description
case strings . EqualFold ( "description" , f ) && s . Description != "" :
dbMask = append ( dbMask , "description" )
newSet . Description = s . Description
2021-11-12 14:55:22 -07:00
case strings . EqualFold ( "SyncIntervalSeconds" , f ) && s . SyncIntervalSeconds == 0 :
nullFields = append ( nullFields , "SyncIntervalSeconds" )
newSet . SyncIntervalSeconds = s . SyncIntervalSeconds
updateSyncInterval = true
case strings . EqualFold ( "SyncIntervalSeconds" , f ) && s . SyncIntervalSeconds != 0 :
dbMask = append ( dbMask , "SyncIntervalSeconds" )
newSet . SyncIntervalSeconds = s . SyncIntervalSeconds
updateSyncInterval = true
2021-11-10 17:08:42 -08:00
case strings . EqualFold ( "PreferredEndpoints" , f ) && len ( s . PreferredEndpoints ) == 0 :
endpointOp = endpointOpDelete
newSet . PreferredEndpoints = s . PreferredEndpoints
case strings . EqualFold ( "PreferredEndpoints" , f ) && len ( s . PreferredEndpoints ) != 0 :
endpointOp = endpointOpUpdate
newSet . PreferredEndpoints = s . PreferredEndpoints
case strings . EqualFold ( "attributes" , strings . Split ( f , "." )[ 0 ]):
// Flag attributes for updating. While multiple masks may be
// sent, we only need to do this once.
updateAttributes = true
default :
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidFieldMask , op , fmt . Sprintf ( "invalid field mask: %s" , f ))
}
}
if updateAttributes {
dbMask = append ( dbMask , "attributes" )
2021-11-28 12:20:46 -08:00
newSet . Attributes , err = patchstruct . PatchBytes ( newSet . Attributes , s . Attributes )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error in set attribute JSON" ))
2021-11-10 17:08:42 -08:00
}
// Flag the record as needing a sync since we've updated
// attributes.
dbMask = append ( dbMask , "NeedSync" )
newSet . NeedSync = true
}
// Get the host catalog for the set and its persisted data.
catalog , persisted , err := r . getCatalog ( ctx , newSet . CatalogId )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "error looking up catalog with id %q" , newSet . CatalogId )))
}
2022-07-20 16:24:46 -07:00
// Assert that the catalog project ID and supplied project ID match.
if catalog . ProjectId != projectId {
return nil , nil , nil , db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "catalog id %q not in project id %q" , newSet . CatalogId , projectId ))
2021-11-10 17:08:42 -08:00
}
// Convert the catalog values to API protobuf values, which is what
// we use for the plugin hook calls.
2024-08-21 18:19:34 +01:00
plgHc , err := toPluginCatalog ( ctx , catalog , plg )
2021-11-10 17:08:42 -08:00
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
currentPlgSet , err := toPluginSet ( ctx , currentSet )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
newPlgSet , err := toPluginSet ( ctx , newSet )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
2024-09-04 15:05:47 +01:00
plgClient , err := pluginClientFactoryFn ( ctx , plgHc , r . plugins )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
2022-08-18 15:39:30 -04:00
if updateAttributes {
if newPlgSet . GetAttributes () != nil {
2024-08-21 18:19:34 +01:00
if err := normalizeSetAttributes ( ctx , plgClient , plgHc , newPlgSet ); err != nil {
2022-08-18 15:39:30 -04:00
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
if newSet . Attributes , err = proto . Marshal ( newPlgSet . GetAttributes ()); err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
}
}
2021-11-10 17:08:42 -08:00
// Get the preferred endpoints to write out.
2024-08-01 11:18:53 -04:00
var preferredEndpoints [] * host . PreferredEndpoint
2021-11-10 17:08:42 -08:00
if endpointOp == endpointOpUpdate {
2024-08-01 11:18:53 -04:00
preferredEndpoints = make ([] * host . PreferredEndpoint , 0 , len ( newSet . PreferredEndpoints ))
2021-11-10 17:08:42 -08:00
for i , e := range newSet . PreferredEndpoints {
obj , err := host . NewPreferredEndpoint ( ctx , newSet . PublicId , uint32 ( i + 1 ), e )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
preferredEndpoints = append ( preferredEndpoints , obj )
}
}
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , projectId , kms . KeyPurposeOplog )
2021-11-10 17:08:42 -08:00
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
}
// If the call to the plugin succeeded, we do not want to call it again if
// the transaction failed and is being retried.
var pluginCalledSuccessfully bool
var setUpdated , preferredEndpointsUpdated bool
var returnedSet * HostSet
var hosts [] * Host
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
2021-11-11 13:51:52 -07:00
func ( reader db . Reader , w db . Writer ) error {
2021-11-10 17:08:42 -08:00
returnedSet = newSet . clone ()
2021-11-12 08:57:10 -07:00
msgs := make ([] * oplog . Message , 0 , len ( preferredEndpoints ) + len ( currentSet . PreferredEndpoints ) + 2 )
2022-01-05 14:38:38 -05:00
ticket , err := w . GetTicket ( ctx , s )
2021-11-10 17:08:42 -08:00
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get ticket" ))
}
if len ( dbMask ) != 0 || len ( nullFields ) != 0 {
var hsOplogMsg oplog . Message
numUpdated , err := w . Update (
ctx ,
returnedSet ,
dbMask ,
nullFields ,
db . NewOplogMsg ( & hsOplogMsg ),
db . WithVersion ( & version ),
)
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if numUpdated != 1 {
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "expected 1 set to be updated, got %d" , numUpdated ))
}
setUpdated = true
msgs = append ( msgs , & hsOplogMsg )
}
switch endpointOp {
2022-02-16 16:12:34 -07:00
case endpointOpDelete , endpointOpUpdate :
2021-11-12 09:04:48 -07:00
if len ( currentSet . PreferredEndpoints ) > 0 {
// Delete all old endpoint entries.
2024-08-01 11:18:53 -04:00
var peps [] * host . PreferredEndpoint
2021-11-12 09:04:48 -07:00
for i := 1 ; i <= len ( currentSet . PreferredEndpoints ); i ++ {
p := host . AllocPreferredEndpoint ()
p . HostSetId , p . Priority = currentSet . GetPublicId (), uint32 ( i )
peps = append ( peps , p )
}
deleteOplogMsgs := make ([] * oplog . Message , 0 , len ( peps ))
2022-02-16 16:12:34 -07:00
if _ , err := w . DeleteItems ( ctx , peps , db . WithDebug ( true ), db . NewOplogMsgs ( & deleteOplogMsgs )); err != nil {
2021-11-12 09:04:48 -07:00
return errors . Wrap ( ctx , err , op )
}
// Only append the oplog message if an operation was actually
// performed.
if len ( deleteOplogMsgs ) > 0 {
2022-02-16 16:12:34 -07:00
preferredEndpointsUpdated = true
2021-11-12 09:04:48 -07:00
msgs = append ( msgs , deleteOplogMsgs ... )
}
2021-11-10 17:08:42 -08:00
}
2022-02-16 16:12:34 -07:00
}
if endpointOp == endpointOpUpdate {
2021-11-10 17:08:42 -08:00
// Create the new entries.
peCreateOplogMsgs := make ([] * oplog . Message , 0 , len ( preferredEndpoints ))
if err := w . CreateItems ( ctx , preferredEndpoints , db . NewOplogMsgs ( & peCreateOplogMsgs )); err != nil {
return err
}
preferredEndpointsUpdated = true
msgs = append ( msgs , peCreateOplogMsgs ... )
}
if ! setUpdated && preferredEndpointsUpdated {
returnedSet . Version = uint32 ( version ) + 1
var hsOplogMsg oplog . Message
numUpdated , err := w . Update (
ctx ,
returnedSet ,
[] string { "version" },
[] string {},
db . NewOplogMsg ( & hsOplogMsg ),
db . WithVersion ( & version ),
)
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if numUpdated != 1 {
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "expected 1 set to be updated, got %d" , numUpdated ))
}
msgs = append ( msgs , & hsOplogMsg )
}
if len ( msgs ) != 0 {
metadata := s . oplog ( oplog . OpType_OP_TYPE_UPDATE )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , ticket , metadata , msgs ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to write oplog" ))
}
}
2021-11-11 13:51:52 -07:00
hsAgg := & hostSetAgg { PublicId : currentSet . GetPublicId ()}
if err := reader . LookupByPublicId ( ctx , hsAgg ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "looking up host after update" ))
}
returnedSet , err = hsAgg . toHostSet ( ctx )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "converting from host aggregate to host after update" ))
}
2021-11-10 17:08:42 -08:00
if ! pluginCalledSuccessfully {
_ , err = plgClient . OnUpdateSet ( ctx , & plgpb . OnUpdateSetRequest {
CurrentSet : currentPlgSet ,
NewSet : newPlgSet ,
Catalog : plgHc ,
Persisted : persisted ,
})
if err != nil {
if status . Code ( err ) != codes . Unimplemented {
return errors . Wrap ( ctx , err , op )
}
}
}
return nil
},
)
if err != nil {
if errors . IsUniqueError ( err ) {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in %s: name %s already exists" , newSet . PublicId , newSet . Name )))
}
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in %s" , newSet . PublicId )))
}
hosts , err = listHostBySetIds ( ctx , r . reader , [] string { returnedSet . PublicId }, opt ... )
if err != nil {
return nil , nil , nil , db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
var numUpdated int
if setUpdated || preferredEndpointsUpdated {
numUpdated = 1
}
2021-11-12 14:55:22 -07:00
switch {
case updateAttributes :
2021-11-10 17:08:42 -08:00
// Request a host sync since we have updated attributes.
2022-01-11 15:55:26 -08:00
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , setSyncJobName , 0 , scheduler . WithRunNow ( true ))
2021-11-12 14:55:22 -07:00
case updateSyncInterval :
2022-01-11 15:55:26 -08:00
var schOpt [] scheduler . Option
2021-11-12 14:55:22 -07:00
tilNextSync := time . Until ( returnedSet . LastSyncTime . AsTime (). Add ( time . Duration ( returnedSet . SyncIntervalSeconds ) * time . Second ))
2022-01-11 15:55:26 -08:00
if tilNextSync <= 0 {
2021-11-12 14:55:22 -07:00
tilNextSync = 0
2022-01-11 15:55:26 -08:00
schOpt = append ( schOpt , scheduler . WithRunNow ( true ))
2021-11-12 14:55:22 -07:00
}
2022-01-11 15:55:26 -08:00
_ = r . scheduler . UpdateJobNextRunInAtLeast ( ctx , setSyncJobName , tilNextSync , schOpt ... )
2021-11-10 17:08:42 -08:00
}
return returnedSet , hosts , plg , numUpdated , nil
}
2021-09-28 16:19:32 -04:00
// LookupSet will look up a host set in the repository and return the host set,
// as well as host IDs that match. If the host set is not found, it will return
2021-10-29 15:35:06 -04:00
// nil, nil, nil. No options are currently supported.
2023-10-30 14:43:12 -07:00
func ( r * Repository ) LookupSet ( ctx context . Context , publicId string , _ ... Option ) ( * HostSet , * plugin . Plugin , error ) {
2021-09-09 10:45:41 -07:00
const op = "plugin.(Repository).LookupSet"
if publicId == "" {
2021-10-29 15:35:06 -04:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "no public id" )
2021-09-28 16:19:32 -04:00
}
2021-10-29 15:35:06 -04:00
sets , plg , err := r . getSets ( ctx , publicId , "" )
2021-09-09 10:45:41 -07:00
if err != nil {
2021-10-29 15:35:06 -04:00
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-09 10:45:41 -07:00
}
2021-09-20 12:48:38 -04:00
switch {
case len ( sets ) == 0 :
2021-10-29 15:35:06 -04:00
return nil , nil , nil // not an error to return no rows for a "lookup"
2021-09-20 12:48:38 -04:00
case len ( sets ) > 1 :
2021-10-29 15:35:06 -04:00
return nil , nil , errors . New ( ctx , errors . NotSpecificIntegrity , op , fmt . Sprintf ( "%s matched more than 1 " , publicId ))
2021-09-28 16:19:32 -04:00
}
2021-10-29 15:35:06 -04:00
return sets [ 0 ], plg , nil
2021-09-09 10:45:41 -07:00
}
2023-10-30 14:43:12 -07:00
// listSets returns a slice of HostSets for the catalogId.
// Supported options:
// - WithLimit which overrides the limit set in the Repository object
// - WithStartPageAfterItem which sets where to start listing from
func ( r * Repository ) listSets ( ctx context . Context , catalogId string , opt ... Option ) ([] * HostSet , * plugin . Plugin , time . Time , error ) {
const op = "plugin.(Repository).listSets"
2021-09-09 10:45:41 -07:00
if catalogId == "" {
2023-10-30 14:43:12 -07:00
return nil , nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing catalog id" )
2021-09-09 10:45:41 -07:00
}
2021-09-20 12:48:38 -04:00
2023-10-30 14:43:12 -07:00
opts := getOpts ( opt ... )
limit := r . defaultLimit
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
2021-09-20 12:48:38 -04:00
}
2023-10-30 14:43:12 -07:00
query := fmt . Sprintf ( listSetsTemplate , limit )
args := [] any { sql . Named ( "catalog_id" , catalogId )}
if opts . withStartPageAfterItem != nil {
query = fmt . Sprintf ( listSetsPageTemplate , limit )
args = append ( args ,
sql . Named ( "last_item_create_time" , opts . withStartPageAfterItem . GetCreateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
return r . querySets ( ctx , query , args )
}
// listSetsRefresh returns a slice of Host sets for the catalogId and the associated plugin.
// Supported options:
// - WithLimit which overrides the limit set in the Repository object
// - WithStartPageAfterItem which sets where to start listing from
func ( r * Repository ) listSetsRefresh ( ctx context . Context , catalogId string , updatedAfter time . Time , opt ... Option ) ([] * HostSet , * plugin . Plugin , time . Time , error ) {
const op = "plugin.(Repository).listSetsRefresh"
switch {
case catalogId == "" :
return nil , nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "no catalog id" )
case updatedAfter . IsZero ():
return nil , nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing updated after time" )
}
opts := getOpts ( opt ... )
limit := r . defaultLimit
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
}
query := fmt . Sprintf ( listSetsRefreshTemplate , limit )
args := [] any {
sql . Named ( "catalog_id" , catalogId ),
sql . Named ( "updated_after_time" , updatedAfter ),
}
if opts . withStartPageAfterItem != nil {
query = fmt . Sprintf ( listSetsRefreshPageTemplate , limit )
args = append ( args ,
sql . Named ( "last_item_update_time" , opts . withStartPageAfterItem . GetUpdateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
return r . querySets ( ctx , query , args )
}
func ( r * Repository ) querySets ( ctx context . Context , query string , args [] any ) ([] * HostSet , * plugin . Plugin , time . Time , error ) {
const op = "plugin.(Repository).querySets"
var sets [] * HostSet
var plg * plugin . Plugin
var transactionTimestamp time . Time
if _ , err := r . 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 foundSets [] * hostSetAgg
for rows . Next () {
if err := r . ScanRows ( ctx , rows , & foundSets ); err != nil {
return err
}
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return err
}
2023-10-30 14:43:12 -07:00
if len ( foundSets ) != 0 {
plg = plugin . NewPlugin ()
plg . PublicId = foundSets [ 0 ]. PluginId
if err := r . LookupByPublicId ( ctx , plg ); err != nil {
return err
}
sets = make ([] * HostSet , 0 , len ( foundSets ))
for _ , ha := range foundSets {
set , err := ha . toHostSet ( ctx )
if err != nil {
return err
}
sets = append ( sets , set )
}
}
transactionTimestamp , err = r . Now ( ctx )
return err
}); err != nil {
return nil , nil , time . Time {}, errors . Wrap ( ctx , err , op )
}
return sets , plg , transactionTimestamp , nil
2021-09-20 12:48:38 -04:00
}
2021-10-25 09:37:05 -07:00
// DeleteSet deletes the host set for the provided id from the repository
// returning a count of the number of records deleted. All options are
// ignored.
2022-07-20 16:24:46 -07:00
func ( r * Repository ) DeleteSet ( ctx context . Context , projectId string , publicId string , _ ... Option ) ( int , error ) {
2021-10-25 09:37:05 -07:00
const op = "plugin.(Repository).DeleteSet"
if publicId == "" {
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no public id" )
}
2022-07-20 16:24:46 -07:00
if projectId == "" {
return db . NoRowsAffected , errors . New ( ctx , errors . InvalidParameter , op , "no project id" )
2021-10-25 09:37:05 -07:00
}
sets , plg , err := r . getSets ( ctx , publicId , "" )
if err != nil {
return db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
if len ( sets ) != 1 {
return db . NoRowsAffected , nil
}
s := sets [ 0 ]
2021-10-28 12:46:42 -07:00
c , p , err := r . getCatalog ( ctx , s . GetCatalogId ())
2021-10-25 09:37:05 -07:00
if err != nil && errors . IsNotFoundError ( err ) {
return db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
if c == nil {
return db . NoRowsAffected , nil
}
2024-08-21 18:19:34 +01:00
plgHc , err := toPluginCatalog ( ctx , c , plg )
2021-10-25 09:37:05 -07:00
if err != nil {
return 0 , errors . Wrap ( ctx , err , op )
}
plgHs , err := toPluginSet ( ctx , s )
if err != nil {
return 0 , errors . Wrap ( ctx , err , op )
}
2024-09-04 15:05:47 +01:00
plgClient , err := pluginClientFactoryFn ( ctx , plgHc , r . plugins )
if err != nil {
return 0 , errors . Wrap ( ctx , err , op )
}
2024-02-13 10:54:48 -05:00
// Even if the plugin returns an error, we ignore it and proceed with
// deleting the set, hence we don't check the error here. This is because we
// may get errors from the plugin that we can't do anything about (say, it's
// already deleted) and we still want to delete the set from the database.
_ , _ = plgClient . OnDeleteSet ( ctx , & plgpb . OnDeleteSetRequest { Catalog : plgHc , Persisted : p , Set : plgHs })
2021-10-25 09:37:05 -07:00
2022-07-20 16:24:46 -07:00
oplogWrapper , err := r . kms . GetWrapper ( ctx , projectId , kms . KeyPurposeOplog )
2021-10-25 09:37:05 -07:00
if err != nil {
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
}
var rowsDeleted int
_ , err = r . writer . DoTx (
ctx , db . StdRetryCnt , db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) ( err error ) {
ds := s . clone ()
rowsDeleted , err = w . Delete ( ctx , ds , db . WithOplog ( oplogWrapper , s . oplog ( oplog . OpType_OP_TYPE_DELETE )))
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if rowsDeleted > 1 {
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 resource would have been deleted" )
}
return nil
},
)
if err != nil {
return db . NoRowsAffected , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "delete failed for %s" , s . PublicId )))
}
return rowsDeleted , nil
}
2023-12-20 11:51:15 -08:00
func ( r * Repository ) getSets ( ctx context . Context , publicId string , catalogId string , opt ... host . Option ) ([] * HostSet , * plugin . Plugin , error ) {
2021-09-20 12:48:38 -04:00
const op = "plugin.(Repository).getSets"
if publicId == "" && catalogId == "" {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing search criteria: both host set id and catalog id are empty" )
2021-09-20 12:48:38 -04:00
}
if publicId != "" && catalogId != "" {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "searching for both a host set id and a catalog id is not supported" )
2021-09-09 10:45:41 -07:00
}
2021-09-20 12:48:38 -04:00
2023-12-20 11:51:15 -08:00
opts , err := host . GetOpts ( opt ... )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2021-09-20 12:48:38 -04:00
limit := r . defaultLimit
2023-12-20 11:51:15 -08:00
if opts . WithLimit != 0 {
2021-09-20 12:48:38 -04:00
// non-zero signals an override of the default limit for the repo.
2023-12-20 11:51:15 -08:00
limit = opts . WithLimit
2021-09-20 12:48:38 -04:00
}
2023-12-20 11:51:15 -08:00
args := make ([] any , 0 , 1 )
2021-09-20 12:48:38 -04:00
var where string
2023-12-20 11:51:15 -08:00
2021-09-20 12:48:38 -04:00
switch {
case publicId != "" :
2023-12-20 11:51:15 -08:00
where , args = "public_id = ?" , append ( args , publicId )
2021-09-20 12:48:38 -04:00
default :
2023-12-20 11:51:15 -08:00
where , args = "catalog_id = ?" , append ( args , catalogId )
2023-10-30 14:43:12 -07:00
}
2023-12-20 11:51:15 -08:00
dbArgs := [] db . Option { db . WithLimit ( limit )}
if opts . WithOrderByCreateTime {
if opts . Ascending {
dbArgs = append ( dbArgs , db . WithOrder ( "create_time asc" ))
} else {
dbArgs = append ( dbArgs , db . WithOrder ( "create_time" ))
}
2021-09-20 12:48:38 -04:00
}
var aggHostSets [] * hostSetAgg
if err := r . reader . SearchWhere ( ctx , & aggHostSets , where , args , dbArgs ... ); err != nil {
2021-09-24 13:16:13 -07:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "in %s" , publicId )))
2021-09-20 12:48:38 -04:00
}
if len ( aggHostSets ) == 0 {
2021-09-24 13:16:13 -07:00
return nil , nil , nil
2021-09-20 12:48:38 -04:00
}
2021-09-24 13:16:13 -07:00
plgId := aggHostSets [ 0 ]. PluginId
2021-09-20 12:48:38 -04:00
sets := make ([] * HostSet , 0 , len ( aggHostSets ))
for _ , agg := range aggHostSets {
2021-09-26 08:17:21 -07:00
hs , err := agg . toHostSet ( ctx )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
2021-09-20 12:48:38 -04:00
}
sets = append ( sets , hs )
}
2023-04-24 19:27:41 -04:00
var plg * plugin . Plugin
2021-09-24 13:16:13 -07:00
if plgId != "" {
plg , err = r . getPlugin ( ctx , plgId )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
}
2021-09-20 12:48:38 -04:00
2021-09-24 13:16:13 -07:00
return sets , plg , nil
2021-09-09 10:45:41 -07:00
}
2021-09-20 12:48:38 -04:00
2021-09-21 09:18:49 -07:00
// toPluginSet returns a host set in the format expected by the host plugin system.
func toPluginSet ( ctx context . Context , in * HostSet ) ( * pb . HostSet , error ) {
2021-09-28 16:19:32 -04:00
const op = "plugin.toPluginSet"
2021-09-21 09:18:49 -07:00
if in == nil {
return nil , errors . New ( ctx , errors . InvalidParameter , op , "nil storage plugin" )
}
2021-11-10 17:08:42 -08:00
var name , description * wrapperspb . StringValue
if inName := in . GetName (); inName != "" {
name = wrapperspb . String ( inName )
}
if inDescription := in . GetDescription (); inDescription != "" {
description = wrapperspb . String ( inDescription )
}
2021-09-21 09:18:49 -07:00
hs := & pb . HostSet {
2021-11-10 17:08:42 -08:00
Id : in . GetPublicId (),
Name : name ,
Description : description ,
2021-11-11 13:51:52 -07:00
PreferredEndpoints : in . PreferredEndpoints ,
2021-09-21 09:18:49 -07:00
}
if in . GetAttributes () != nil {
attrs := & structpb . Struct {}
if err := proto . Unmarshal ( in . GetAttributes (), attrs ); err != nil {
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to marshal attributes" ))
}
2022-04-13 12:58:33 -04:00
hs . Attrs = & pb . HostSet_Attributes {
Attributes : attrs ,
}
2021-09-21 09:18:49 -07:00
}
return hs , nil
}
2021-09-26 08:17:21 -07:00
2023-10-30 14:43:12 -07:00
// listDeletedSetIds lists the public IDs of any hosts deleted since the timestamp provided,
// and the timestamp of the transaction within which the hosts were listed.
func ( r * Repository ) listDeletedSetIds ( ctx context . Context , since time . Time ) ([] string , time . Time , error ) {
const op = "static.(Repository).listDeletedHostSetIds"
var deleteHostSets [] * deletedHostSet
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( r db . Reader , _ db . Writer ) error {
if err := r . SearchWhere ( ctx , & deleteHostSets , "delete_time >= ?" , [] any { since }); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query deleted host sets" ))
}
var err error
transactionTimestamp , err = r . Now ( ctx )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get transaction timestamp" ))
}
return nil
}); err != nil {
return nil , time . Time {}, err
}
var hostSetIds [] string
for _ , t := range deleteHostSets {
hostSetIds = append ( hostSetIds , t . PublicId )
}
return hostSetIds , transactionTimestamp , nil
}
// estimatedSetCount returns an estimate of the total number of plugin host sets.
func ( r * Repository ) estimatedSetCount ( ctx context . Context ) ( int , error ) {
const op = "plugin.(Repository).estimatedHostSetCount"
rows , err := r . reader . Query ( ctx , estimateCountHostSets , nil )
if err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query plugin host sets" ))
}
var count int
for rows . Next () {
if err := r . reader . ScanRows ( ctx , rows , & count ); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query plugin host sets" ))
}
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query plugin host sets" ))
}
2023-10-30 14:43:12 -07:00
return count , nil
}
2021-09-26 08:17:21 -07:00
// Endpoints provides all the endpoints available for a given set id.
// An error is returned if the set, related catalog, or related plugin are
// unable to be retrieved. If a host does not contain an addressible endpoint
// it is not included in the resulting slice of endpoints.
func ( r * Repository ) Endpoints ( ctx context . Context , setIds [] string ) ([] * host . Endpoint , error ) {
const op = "plugin.(Repository).Endpoints"
if len ( setIds ) == 0 {
return nil , errors . New ( ctx , errors . InvalidParameter , op , "no set ids" )
}
2021-09-28 16:19:32 -04:00
// Fist, look up the sets corresponding to the set IDs
2021-09-26 08:17:21 -07:00
var setAggs [] * hostSetAgg
2022-10-25 18:29:38 -05:00
if err := r . reader . SearchWhere ( ctx , & setAggs , "public_id in (?)" , [] any { setIds }); err != nil {
2021-09-26 08:17:21 -07:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "can't retrieve sets %v" , setIds )))
}
if len ( setAggs ) == 0 {
return nil , nil
}
2021-11-01 09:07:56 -07:00
setIdToSet := make ( map [ string ] * HostSet , len ( setAggs ))
for _ , s := range setAggs {
var err error
setIdToSet [ s . PublicId ], err = s . toHostSet ( ctx )
2021-09-26 08:17:21 -07:00
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
2021-11-01 09:07:56 -07:00
}
2021-09-26 08:17:21 -07:00
2021-11-01 09:07:56 -07:00
var setMembers [] * HostSetMember
2022-10-25 18:29:38 -05:00
if err := r . reader . SearchWhere ( ctx , & setMembers , "set_id in (?)" , [] any { setIds }); err != nil {
2021-11-01 09:07:56 -07:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "can't retrieve set members for sets %v" , setIds )))
}
if len ( setMembers ) == 0 {
return nil , nil
2021-09-26 08:17:21 -07:00
}
2021-11-01 09:07:56 -07:00
hostIdToSetIds := make ( map [ string ][] string )
for _ , m := range setMembers {
hostIdToSetIds [ m . GetHostId ()] = append ( hostIdToSetIds [ m . GetHostId ()], m . GetSetId ())
2021-09-26 08:17:21 -07:00
}
2021-11-01 09:07:56 -07:00
var hostIds [] string
for hid := range hostIdToSetIds {
hostIds = append ( hostIds , hid )
2021-09-26 08:17:21 -07:00
}
2021-11-01 09:07:56 -07:00
var hostAggs [] * hostAgg
2022-10-25 18:29:38 -05:00
if err := r . reader . SearchWhere ( ctx , & hostAggs , "public_id in (?)" , [] any { hostIds }); err != nil {
2021-11-01 09:07:56 -07:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "can't retrieve hosts %v" , hostIds )))
2021-09-26 08:17:21 -07:00
}
2021-11-01 09:07:56 -07:00
if len ( hostAggs ) == 0 {
return nil , nil
2021-09-26 08:17:21 -07:00
}
var es [] * host . Endpoint
2021-11-01 09:07:56 -07:00
for _ , ha := range hostAggs {
h := ha . toHost ()
for _ , sId := range hostIdToSetIds [ h . GetPublicId ()] {
s := setIdToSet [ sId ]
2021-11-11 11:28:08 -05:00
pref , err := endpoint . NewPreferencer ( ctx , endpoint . WithPreferenceOrder ( s . PreferredEndpoints ))
2021-11-01 09:07:56 -07:00
if err != nil {
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "getting preferencer for set %q" , sId )))
}
var opts [] endpoint . Option
if len ( h . GetIpAddresses ()) > 0 {
opts = append ( opts , endpoint . WithIpAddrs ( h . GetIpAddresses ()))
}
if len ( h . GetDnsNames ()) > 0 {
2022-02-10 15:59:35 -05:00
opts = append ( opts , endpoint . WithDnsNames ( h . GetDnsNames ()))
2021-11-01 09:07:56 -07:00
}
addr , err := pref . Choose ( ctx , opts ... )
2021-09-26 08:17:21 -07:00
if err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
2021-11-01 09:07:56 -07:00
if addr == "" {
continue
2021-09-26 08:17:21 -07:00
}
2021-11-01 09:07:56 -07:00
es = append ( es , & host . Endpoint {
HostId : h . GetPublicId (),
SetId : sId ,
Address : addr ,
2021-09-26 08:17:21 -07:00
})
}
}
2021-11-01 09:07:56 -07:00
2021-09-26 08:17:21 -07:00
return es , nil
}