2020-08-26 13:58:46 -04:00
package target
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-26 13:58:46 -04:00
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
)
// Clonable provides a cloning interface
type Cloneable interface {
Clone () interface {}
}
// Repository is the target database repository
type Repository struct {
reader db . Reader
writer db . Writer
kms * kms . Kms
// defaultLimit provides a default for limiting the number of results returned from the repo
defaultLimit int
}
// NewRepository creates a new target Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository ( r db . Reader , w db . Writer , kms * kms . Kms , opt ... Option ) ( * Repository , error ) {
2021-01-22 13:46:20 -08:00
const op = "target.NewRepository"
2020-08-26 13:58:46 -04:00
if r == nil {
2021-01-22 13:46:20 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "nil reader" )
2020-08-26 13:58:46 -04:00
}
if w == nil {
2021-01-22 13:46:20 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "nil writer" )
2020-08-26 13:58:46 -04:00
}
if kms == nil {
2021-01-22 13:46:20 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "nil kms" )
2020-08-26 13:58:46 -04:00
}
opts := getOpts ( opt ... )
if opts . withLimit == 0 {
// zero signals the boundary defaults should be used.
opts . withLimit = db . DefaultLimit
}
return & Repository {
reader : r ,
writer : w ,
kms : kms ,
defaultLimit : opts . withLimit ,
}, nil
}
// LookupTarget will look up a target in the repository and return the target
// with its host set ids. If the target is not found, it will return nil, nil, nil.
// No options are currently supported.
2020-10-22 08:40:00 -04:00
func ( r * Repository ) LookupTarget ( ctx context . Context , publicIdOrName string , opt ... Option ) ( Target , [] * TargetSet , error ) {
2021-01-22 13:46:20 -08:00
const op = "target.(Repository).LookupTarget"
2020-10-22 08:40:00 -04:00
opts := getOpts ( opt ... )
if publicIdOrName == "" {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing public id" )
2020-08-26 13:58:46 -04:00
}
2020-10-22 08:40:00 -04:00
var where [] string
var whereArgs [] interface {}
nameEmpty := opts . withName == ""
scopeIdEmpty := opts . withScopeId == ""
scopeNameEmpty := opts . withScopeName == ""
if ! nameEmpty {
if opts . withName != publicIdOrName {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "name passed in but does not match publicId" )
2020-10-22 08:40:00 -04:00
}
where , whereArgs = append ( where , "lower(name) = lower(?)" ), append ( whereArgs , opts . withName )
switch {
case scopeIdEmpty && scopeNameEmpty :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "using name but both scope ID and scope name are empty" )
2020-10-22 08:40:00 -04:00
case ! scopeIdEmpty && ! scopeNameEmpty :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "using name but both scope ID and scope name are set" )
2020-10-22 08:40:00 -04:00
case ! scopeIdEmpty :
where , whereArgs = append ( where , "scope_id = ?" ), append ( whereArgs , opts . withScopeId )
case ! scopeNameEmpty :
where , whereArgs = append ( where , "scope_id = (select public_id from iam_scope where lower(name) = lower(?))" ), append ( whereArgs , opts . withScopeName )
default :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "unknown combination of parameters" )
2020-10-22 08:40:00 -04:00
}
} else {
switch {
case ! scopeIdEmpty :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "passed in scope ID when using target ID for lookup" )
2020-10-22 08:40:00 -04:00
case ! scopeNameEmpty :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "passed in scope name when using target ID for lookup" )
2020-10-22 08:40:00 -04:00
}
}
2020-08-26 13:58:46 -04:00
target := allocTargetView ()
2020-10-22 08:40:00 -04:00
target . PublicId = publicIdOrName
2020-08-26 13:58:46 -04:00
var hostSets [] * TargetSet
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( read db . Reader , w db . Writer ) error {
2020-10-22 08:40:00 -04:00
var lookupErr error
switch where {
case nil :
lookupErr = read . LookupById ( ctx , & target )
default :
target . PublicId = ""
lookupErr = read . LookupWhere ( ctx , & target , strings . Join ( where , " and " ), whereArgs ... )
}
if lookupErr != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( lookupErr , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , publicIdOrName )))
2020-08-26 13:58:46 -04:00
}
var err error
if hostSets , err = fetchSets ( ctx , read , target . PublicId ); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
return nil
},
)
if err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-09-01 15:05:46 -07:00
return nil , nil , nil
}
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
2021-05-17 15:36:45 -04:00
subtype , err := target . targetSubtype ()
2020-08-26 13:58:46 -04:00
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
2021-05-17 15:36:45 -04:00
return subtype , hostSets , nil
2020-08-26 13:58:46 -04:00
}
func fetchSets ( ctx context . Context , r db . Reader , targetId string ) ([] * TargetSet , error ) {
2021-01-22 13:46:20 -08:00
const op = "target.fetchSets"
2020-08-26 13:58:46 -04:00
var hostSets [] * TargetSet
if err := r . SearchWhere ( ctx , & hostSets , "target_id = ?" , [] interface {}{ targetId }); err != nil {
2021-01-22 13:46:20 -08:00
return nil , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
if len ( hostSets ) == 0 {
return nil , nil
}
return hostSets , nil
}
// ListTargets in targets in a scope. Supports the WithScopeId, WithLimit, WithTargetType options.
func ( r * Repository ) ListTargets ( ctx context . Context , opt ... Option ) ([] Target , error ) {
2021-01-22 13:46:20 -08:00
const op = "target.(Repository).ListTargets"
2020-08-26 13:58:46 -04:00
opts := getOpts ( opt ... )
2021-01-25 19:37:40 -05:00
if len ( opts . withScopeIds ) == 0 && opts . withUserId == "" {
return nil , errors . New ( errors . InvalidParameter , op , "must specify either scope id or user id" )
2020-08-26 13:58:46 -04:00
}
// TODO (jimlambrt 8/2020) - implement WithUserId() optional filtering.
var where [] string
var args [] interface {}
2021-01-25 19:37:40 -05:00
if len ( opts . withScopeIds ) != 0 {
where , args = append ( where , "scope_id in (?)" ), append ( args , opts . withScopeIds )
2020-08-26 13:58:46 -04:00
}
if opts . withTargetType != nil {
where , args = append ( where , "type = ?" ), append ( args , opts . withTargetType . String ())
}
var foundTargets [] * targetView
err := r . list ( ctx , & foundTargets , strings . Join ( where , " and " ), args , opt ... )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
targets := make ([] Target , 0 , len ( foundTargets ))
for _ , t := range foundTargets {
2021-05-17 15:36:45 -04:00
subtype , err := t . targetSubtype ()
2020-08-26 13:58:46 -04:00
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
2021-05-17 15:36:45 -04:00
targets = append ( targets , subtype )
2020-08-26 13:58:46 -04:00
}
return targets , nil
}
// list will return a listing of resources and honor the WithLimit option or the
// repo defaultLimit
func ( r * Repository ) list ( ctx context . Context , resources interface {}, where string , args [] interface {}, opt ... Option ) error {
2021-01-22 13:46:20 -08:00
const op = "target.(Repository).list"
2020-08-26 13:58:46 -04:00
opts := getOpts ( opt ... )
limit := r . defaultLimit
var dbOpts [] db . Option
if opts . withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts . withLimit
}
dbOpts = append ( dbOpts , db . WithLimit ( limit ))
2021-01-22 13:46:20 -08:00
if err := r . reader . SearchWhere ( ctx , resources , where , args , dbOpts ... ); err != nil {
return errors . Wrap ( err , op )
}
return nil
2020-08-26 13:58:46 -04:00
}
// DeleteTarget will delete a target from the repository.
2021-01-22 13:46:20 -08:00
func ( r * Repository ) DeleteTarget ( ctx context . Context , publicId string , _ ... Option ) ( int , error ) {
const op = "target.(Repository).DeleteTarget"
2020-08-26 13:58:46 -04:00
if publicId == "" {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing public id" )
2020-08-26 13:58:46 -04:00
}
t := allocTargetView ()
t . PublicId = publicId
if err := r . reader . LookupByPublicId ( ctx , & t ); err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , publicId )))
2020-08-26 13:58:46 -04:00
}
var metadata oplog . Metadata
var deleteTarget interface {}
switch t . Type {
case TcpTargetType . String ():
tcpT := allocTcpTarget ()
tcpT . PublicId = publicId
deleteTarget = & tcpT
metadata = tcpT . oplog ( oplog . OpType_OP_TYPE_DELETE )
default :
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , fmt . Sprintf ( "%s is an unsupported target type %s" , publicId , t . Type ))
2020-08-26 13:58:46 -04:00
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , t . ScopeId , kms . KeyPurposeOplog )
if err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-26 13:58:46 -04:00
}
var rowsDeleted int
var deleteResource interface {}
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
deleteResource = deleteTarget .( Cloneable ). Clone ()
rowsDeleted , err = w . Delete (
ctx ,
deleteResource ,
db . WithOplog ( oplogWrapper , metadata ),
)
2021-01-22 13:46:20 -08:00
if err != nil {
return errors . Wrap ( err , op )
}
if rowsDeleted > 1 {
2020-08-26 13:58:46 -04:00
// return err, which will result in a rollback of the delete
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , "more than 1 resource would have been deleted" )
2020-08-26 13:58:46 -04:00
}
2021-01-22 13:46:20 -08:00
return nil
2020-08-26 13:58:46 -04:00
},
)
2021-01-22 13:46:20 -08:00
if err != nil {
return db . NoRowsAffected , errors . Wrap ( err , op )
}
return rowsDeleted , nil
2020-08-26 13:58:46 -04:00
}
// update a target in the db repository with an oplog entry.
// It currently supports no options.
2021-01-22 13:46:20 -08:00
func ( r * Repository ) update ( ctx context . Context , target Target , version uint32 , fieldMaskPaths [] string , setToNullPaths [] string , _ ... Option ) ( Target , [] * TargetSet , int , error ) {
const op = "target.(Repository).update"
2020-08-26 13:58:46 -04:00
if version == 0 {
2021-01-22 13:46:20 -08:00
return nil , nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-08-26 13:58:46 -04:00
}
if target == nil {
2021-01-22 13:46:20 -08:00
return nil , nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "nil target" )
2020-08-26 13:58:46 -04:00
}
cloner , ok := target .( Cloneable )
if ! ok {
2021-01-22 13:46:20 -08:00
return nil , nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "target is not cloneable" )
2020-08-26 13:58:46 -04:00
}
dbOpts := [] db . Option {
db . WithVersion ( & version ),
}
scopeId := target . GetScopeId ()
if scopeId == "" {
t := allocTargetView ()
t . PublicId = target . GetPublicId ()
if err := r . reader . LookupByPublicId ( ctx , & t ); err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "lookup failed for %s" , t . PublicId )))
2020-08-26 13:58:46 -04:00
}
scopeId = t . ScopeId
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , scopeId , kms . KeyPurposeOplog )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-26 13:58:46 -04:00
}
metadata := target . oplog ( oplog . OpType_OP_TYPE_UPDATE )
dbOpts = append ( dbOpts , db . WithOplog ( oplogWrapper , metadata ))
var rowsUpdated int
var returnedTarget interface {}
var hostSets [] * TargetSet
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
returnedTarget = cloner . Clone ()
rowsUpdated , err = w . Update (
ctx ,
returnedTarget ,
fieldMaskPaths ,
setToNullPaths ,
dbOpts ... ,
)
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
2021-01-22 13:46:20 -08:00
if rowsUpdated > 1 {
2020-08-26 13:58:46 -04:00
// return err, which will result in a rollback of the update
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , "more than 1 resource would have been updated" )
2020-08-26 13:58:46 -04:00
}
2021-01-22 13:46:20 -08:00
2020-08-26 13:58:46 -04:00
if hostSets , err = fetchSets ( ctx , reader , target . GetPublicId ()); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
2021-01-22 13:46:20 -08:00
return nil
2020-08-26 13:58:46 -04:00
},
)
2021-01-22 13:46:20 -08:00
if err != nil {
return nil , nil , db . NoRowsAffected , errors . Wrap ( err , op )
}
return returnedTarget .( Target ), hostSets , rowsUpdated , nil
2020-08-26 13:58:46 -04:00
}
2020-09-04 17:11:51 -04:00
// AddTargetHostSets provides the ability to add host sets (hostSetIds) to a
2020-08-26 13:58:46 -04:00
// target (targetId). The target's current db version must match the
// targetVersion or an error will be returned. The target and a list of
// current host set ids will be returned on success. Zero is not a valid value
// for the WithVersion option and will return an error.
2021-01-22 13:46:20 -08:00
func ( r * Repository ) AddTargetHostSets ( ctx context . Context , targetId string , targetVersion uint32 , hostSetIds [] string , _ ... Option ) ( Target , [] * TargetSet , error ) {
const op = "target.(Repository).AddTargetHostSets"
2020-08-26 13:58:46 -04:00
if targetId == "" {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing target id" )
2020-08-26 13:58:46 -04:00
}
if targetVersion == 0 {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-08-26 13:58:46 -04:00
}
if len ( hostSetIds ) == 0 {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing host set ids" )
2020-08-26 13:58:46 -04:00
}
newHostSets := make ([] interface {}, 0 , len ( hostSetIds ))
for _ , id := range hostSetIds {
ths , err := NewTargetHostSet ( targetId , id )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "unable to create in memory target host set" ))
2020-08-26 13:58:46 -04:00
}
newHostSets = append ( newHostSets , ths )
}
t := allocTargetView ()
t . PublicId = targetId
if err := r . reader . LookupByPublicId ( ctx , & t ); err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , targetId )))
2020-08-26 13:58:46 -04:00
}
var metadata oplog . Metadata
var target interface {}
switch t . Type {
case TcpTargetType . String ():
tcpT := allocTcpTarget ()
tcpT . PublicId = t . PublicId
tcpT . Version = targetVersion + 1
target = & tcpT
metadata = tcpT . oplog ( oplog . OpType_OP_TYPE_UPDATE )
metadata [ "op-type" ] = append ( metadata [ "op-type" ], oplog . OpType_OP_TYPE_CREATE . String ())
default :
2021-01-22 13:46:20 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , fmt . Sprintf ( "%s is an unsupported target type %s" , t . PublicId , t . Type ))
2020-08-26 13:58:46 -04:00
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , t . GetScopeId (), kms . KeyPurposeOplog )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-26 13:58:46 -04:00
}
var currentHostSets [] * TargetSet
var updatedTarget interface {}
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
msgs := make ([] * oplog . Message , 0 , 2 )
targetTicket , err := w . GetTicket ( target )
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to get ticket" ))
2020-08-26 13:58:46 -04:00
}
updatedTarget = target .( Cloneable ). Clone ()
var targetOplogMsg oplog . Message
rowsUpdated , err := w . Update ( ctx , updatedTarget , [] string { "Version" }, nil , db . NewOplogMsg ( & targetOplogMsg ), db . WithVersion ( & targetVersion ))
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to update target version" ))
2020-08-26 13:58:46 -04:00
}
if rowsUpdated != 1 {
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "updated target and %d rows updated" , rowsUpdated ))
2020-08-26 13:58:46 -04:00
}
msgs = append ( msgs , & targetOplogMsg )
hostSetsOplogMsgs := make ([] * oplog . Message , 0 , len ( newHostSets ))
if err := w . CreateItems ( ctx , newHostSets , db . NewOplogMsgs ( & hostSetsOplogMsgs )); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to add target host sets" ))
2020-08-26 13:58:46 -04:00
}
msgs = append ( msgs , hostSetsOplogMsgs ... )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , targetTicket , metadata , msgs ); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to write oplog" ))
2020-08-26 13:58:46 -04:00
}
currentHostSets , err = fetchSets ( ctx , reader , targetId )
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to retrieve current host sets after adds" ))
2020-08-26 13:58:46 -04:00
}
return nil
},
)
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "error creating sets" ))
2020-08-26 13:58:46 -04:00
}
return updatedTarget .( Target ), currentHostSets , nil
}
// DeleteTargeHostSets deletes host sets from a target (targetId). The target's
// current db version must match the targetVersion or an error will be returned.
// Zero is not a valid value for the WithVersion option and will return an
// error.
2021-01-22 13:46:20 -08:00
func ( r * Repository ) DeleteTargeHostSets ( ctx context . Context , targetId string , targetVersion uint32 , hostSetIds [] string , _ ... Option ) ( int , error ) {
const op = "target.(Repository).DeleteTargeHostSets"
2020-08-26 13:58:46 -04:00
if targetId == "" {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing target id" )
2020-08-26 13:58:46 -04:00
}
if targetVersion == 0 {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-08-26 13:58:46 -04:00
}
if len ( hostSetIds ) == 0 {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing host set ids" )
2020-08-26 13:58:46 -04:00
}
deleteTargeHostSets := make ([] interface {}, 0 , len ( hostSetIds ))
for _ , id := range hostSetIds {
ths , err := NewTargetHostSet ( targetId , id )
if err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to create in memory target host set" ))
2020-08-26 13:58:46 -04:00
}
deleteTargeHostSets = append ( deleteTargeHostSets , ths )
}
t := allocTargetView ()
t . PublicId = targetId
if err := r . reader . LookupByPublicId ( ctx , & t ); err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , targetId )))
2020-08-26 13:58:46 -04:00
}
var metadata oplog . Metadata
var target interface {}
switch t . Type {
case TcpTargetType . String ():
tcpT := allocTcpTarget ()
tcpT . PublicId = t . PublicId
tcpT . Version = targetVersion + 1
target = & tcpT
metadata = tcpT . oplog ( oplog . OpType_OP_TYPE_UPDATE )
metadata [ "op-type" ] = append ( metadata [ "op-type" ], oplog . OpType_OP_TYPE_DELETE . String ())
default :
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , fmt . Sprintf ( "%s is an unsupported target type %s" , t . PublicId , t . Type ))
2020-08-26 13:58:46 -04:00
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , t . GetScopeId (), kms . KeyPurposeOplog )
if err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-26 13:58:46 -04:00
}
var totalRowsDeleted int
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
msgs := make ([] * oplog . Message , 0 , 2 )
targetTicket , err := w . GetTicket ( target )
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to get ticket" ))
2020-08-26 13:58:46 -04:00
}
updatedTarget := target .( Cloneable ). Clone ()
var targetOplogMsg oplog . Message
rowsUpdated , err := w . Update ( ctx , updatedTarget , [] string { "Version" }, nil , db . NewOplogMsg ( & targetOplogMsg ), db . WithVersion ( & targetVersion ))
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to update target version" ))
2020-08-26 13:58:46 -04:00
}
if rowsUpdated != 1 {
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "updated target and %d rows updated" , rowsUpdated ))
2020-08-26 13:58:46 -04:00
}
msgs = append ( msgs , & targetOplogMsg )
hostSetsOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteTargeHostSets ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteTargeHostSets , db . NewOplogMsgs ( & hostSetsOplogMsgs ))
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to delete target host sets" ))
2020-08-26 13:58:46 -04:00
}
if rowsDeleted != len ( deleteTargeHostSets ) {
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "target host sets deleted %d did not match request for %d" , rowsDeleted , len ( deleteTargeHostSets )))
2020-08-26 13:58:46 -04:00
}
totalRowsDeleted += rowsDeleted
msgs = append ( msgs , hostSetsOplogMsgs ... )
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , targetTicket , metadata , msgs ); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to write oplog" ))
2020-08-26 13:58:46 -04:00
}
return nil
},
)
if err != nil {
2021-01-22 13:46:20 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
return totalRowsDeleted , nil
}
// SetTargetHostSets will set the target's host sets. Set add and/or delete
// target host sets as need to reconcile the existing sets with the sets
// requested. If hostSetIds is empty, the target host sets will be cleared. Zero
// is not a valid value for the WithVersion option and will return an error.
2021-01-22 13:46:20 -08:00
func ( r * Repository ) SetTargetHostSets ( ctx context . Context , targetId string , targetVersion uint32 , hostSetIds [] string , _ ... Option ) ([] * TargetSet , int , error ) {
const op = "target.(Repository).SetTargetHostSets"
2020-08-26 13:58:46 -04:00
if targetId == "" {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing target id" )
2020-08-26 13:58:46 -04:00
}
if targetVersion == 0 {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-08-26 13:58:46 -04:00
}
t := allocTargetView ()
t . PublicId = targetId
if err := r . reader . LookupByPublicId ( ctx , & t ); err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , targetId )))
2020-08-26 13:58:46 -04:00
}
// NOTE: calculating that to set can safely happen outside of the write
// transaction since we're using targetVersion to ensure that the only
// operate on the same set of data from these queries that calculate the
// set.
// TODO(mgaffney) 08/2020: Use SQL to calculate changes.
foundThs , err := fetchSets ( ctx , r . reader , targetId )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to search for existing target host sets" ))
2020-08-26 13:58:46 -04:00
}
found := map [ string ] * TargetSet {}
for _ , s := range foundThs {
found [ s . PublicId ] = s
}
addHostSets := make ([] interface {}, 0 , len ( hostSetIds ))
for _ , id := range hostSetIds {
if _ , ok := found [ id ]; ok {
// found a match, so do nothing (we want to keep it), but remove it
// from found
delete ( found , id )
continue
}
hs , err := NewTargetHostSet ( targetId , id )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to create in memory target host set" ))
2020-08-26 13:58:46 -04:00
}
addHostSets = append ( addHostSets , hs )
}
deleteHostSets := make ([] interface {}, 0 , len ( hostSetIds ))
if len ( found ) > 0 {
for _ , s := range found {
hs , err := NewTargetHostSet ( targetId , s . PublicId )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( " unable to create in memory target host set" ))
2020-08-26 13:58:46 -04:00
}
deleteHostSets = append ( deleteHostSets , hs )
}
}
if len ( addHostSets ) == 0 && len ( deleteHostSets ) == 0 {
return foundThs , db . NoRowsAffected , nil
}
var metadata oplog . Metadata
var target interface {}
switch t . Type {
case TcpTargetType . String ():
tcpT := allocTcpTarget ()
tcpT . PublicId = t . PublicId
tcpT . Version = targetVersion + 1
target = & tcpT
metadata = tcpT . oplog ( oplog . OpType_OP_TYPE_UPDATE )
default :
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , fmt . Sprintf ( "%s is an unsupported target type %s" , t . PublicId , t . Type ))
2020-08-26 13:58:46 -04:00
}
oplogWrapper , err := r . kms . GetWrapper ( ctx , t . GetScopeId (), kms . KeyPurposeOplog )
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( "unable to get oplog wrapper" ))
2020-08-26 13:58:46 -04:00
}
var totalRowsAffected int
var currentHostSets [] * TargetSet
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
msgs := make ([] * oplog . Message , 0 , 2 )
targetTicket , err := w . GetTicket ( target )
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to get ticket" ))
2020-08-26 13:58:46 -04:00
}
updatedTarget := target .( Cloneable ). Clone ()
var targetOplogMsg oplog . Message
rowsUpdated , err := w . Update ( ctx , updatedTarget , [] string { "Version" }, nil , db . NewOplogMsg ( & targetOplogMsg ), db . WithVersion ( & targetVersion ))
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to update target version" ))
2020-08-26 13:58:46 -04:00
}
if rowsUpdated != 1 {
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "set target host sets: updated target and %d rows updated" , rowsUpdated ))
2020-08-26 13:58:46 -04:00
}
msgs = append ( msgs , & targetOplogMsg )
// Write the new ones in
if len ( addHostSets ) > 0 {
hostSetOplogMsgs := make ([] * oplog . Message , 0 , len ( addHostSets ))
if err := w . CreateItems ( ctx , addHostSets , db . NewOplogMsgs ( & hostSetOplogMsgs )); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to add target host sets" ))
2020-08-26 13:58:46 -04:00
}
totalRowsAffected += len ( addHostSets )
msgs = append ( msgs , hostSetOplogMsgs ... )
metadata [ "op-type" ] = append ( metadata [ "op-type" ], oplog . OpType_OP_TYPE_CREATE . String ())
}
// Anything we didn't take out of found needs to be removed
if len ( deleteHostSets ) > 0 {
hostSetOplogMsgs := make ([] * oplog . Message , 0 , len ( deleteHostSets ))
rowsDeleted , err := w . DeleteItems ( ctx , deleteHostSets , db . NewOplogMsgs ( & hostSetOplogMsgs ))
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to delete target host set" ))
2020-08-26 13:58:46 -04:00
}
if rowsDeleted != len ( deleteHostSets ) {
2021-01-22 13:46:20 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "target host sets deleted %d did not match request for %d" , rowsDeleted , len ( deleteHostSets )))
2020-08-26 13:58:46 -04:00
}
totalRowsAffected += rowsDeleted
msgs = append ( msgs , hostSetOplogMsgs ... )
metadata [ "op-type" ] = append ( metadata [ "op-type" ], oplog . OpType_OP_TYPE_DELETE . String ())
}
if err := w . WriteOplogEntryWith ( ctx , oplogWrapper , targetTicket , metadata , msgs ); err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to write oplog" ))
2020-08-26 13:58:46 -04:00
}
currentHostSets , err = fetchSets ( ctx , reader , targetId )
if err != nil {
2021-01-22 13:46:20 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to retrieve current target host sets after set" ))
2020-08-26 13:58:46 -04:00
}
return nil
},
)
if err != nil {
2021-01-22 13:46:20 -08:00
return nil , db . NoRowsAffected , errors . Wrap ( err , op )
2020-08-26 13:58:46 -04:00
}
return currentHostSets , totalRowsAffected , nil
}