From 760671f223227bf19ca3ddb9eb4354bec6d3594d Mon Sep 17 00:00:00 2001 From: Damian Debkowski Date: Tue, 6 Dec 2022 12:28:55 -0800 Subject: [PATCH] feat(target): Support connecting to an address associated to a Target --- .../storage/target/store/v1/target.proto | 14 + internal/target/address.go | 113 ++++++ internal/target/address_test.go | 67 ++++ internal/target/options.go | 9 + internal/target/repository.go | 238 +++++++----- internal/target/repository_address.go | 21 ++ .../target/repository_credential_source.go | 11 +- internal/target/repository_host_source.go | 24 +- internal/target/store/target.pb.go | 338 +++++++++++------- internal/target/target.go | 7 +- internal/target/targettest/target.go | 12 +- internal/target/tcp/exports_test.go | 14 +- .../target/tcp/repository_host_source_test.go | 44 ++- .../target/tcp/repository_tcp_target_test.go | 248 ++++++++----- internal/target/tcp/target.go | 14 +- internal/target/tcp/target_test.go | 4 +- internal/target/tcp/testing.go | 7 + internal/target/testing.go | 23 ++ 18 files changed, 899 insertions(+), 309 deletions(-) create mode 100644 internal/target/address.go create mode 100644 internal/target/address_test.go create mode 100644 internal/target/repository_address.go diff --git a/internal/proto/controller/storage/target/store/v1/target.proto b/internal/proto/controller/storage/target/store/v1/target.proto index 374d440bb..4e9f44272 100644 --- a/internal/proto/controller/storage/target/store/v1/target.proto +++ b/internal/proto/controller/storage/target/store/v1/target.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package controller.storage.target.store.v1; +import "controller/custom_options/v1/options.proto"; import "controller/storage/timestamp/v1/timestamp.proto"; option go_package = "github.com/hashicorp/boundary/internal/target/store;store"; @@ -79,6 +80,19 @@ message TargetHostSet { timestamp.v1.Timestamp create_time = 30; } +message TargetAddress { + // target_id of the Target + // @inject_tag: gorm:"primary_key" + string target_id = 10; + + // address associated to the Target + // @inject_tag: `gorm:"not_null"` + string address = 20 [(custom_options.v1.mask_mapping) = { + this: "address" + that: "address" + }]; +} + message CredentialLibrary { // target_id of the Target // @inject_tag: gorm:"primary_key" diff --git a/internal/target/address.go b/internal/target/address.go new file mode 100644 index 000000000..72d05584a --- /dev/null +++ b/internal/target/address.go @@ -0,0 +1,113 @@ +package target + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target/store" + "google.golang.org/protobuf/proto" +) + +const ( + DefaultTargetAddressTableName = "target_address" +) + +// An Address represents the relationship between a target and a +// network address. +type Address struct { + *store.TargetAddress + tableName string `gorm:"-"` +} + +// Ensure Address implements interfaces +var ( + _ db.VetForWriter = (*Address)(nil) + _ oplog.ReplayableMessage = (*Address)(nil) +) + +// NewAddress creates a new in memory address. No options are +// currently supported. +func NewAddress(targetId, address string, _ ...Option) (*Address, error) { + const op = "target.NewAddress" + if targetId == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing target id") + } + if address == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing address") + } + t := &Address{ + TargetAddress: &store.TargetAddress{ + TargetId: targetId, + Address: address, + }, + } + return t, nil +} + +// Clone creates a clone of the target address +func (t *Address) Clone() any { + cp := proto.Clone(t.TargetAddress) + return &Address{ + TargetAddress: cp.(*store.TargetAddress), + } +} + +// VetForWrite implements db.VetForWrite() interface and validates the target +// address before it's written. +func (t *Address) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { + const op = "target.(Address).VetForWrite" + if opType == db.CreateOp { + if t.GetTargetId() == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing target id") + } + if t.GetAddress() == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing address") + } + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (t *Address) TableName() string { + if t.tableName != "" { + return t.tableName + } + return DefaultTargetAddressTableName +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (t *Address) SetTableName(n string) { + t.tableName = n +} + +func (t *Address) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{t.GetTargetId()}, + "resource-type": []string{"target address"}, + "op-type": []string{op.String()}, + } + return metadata +} + +func (t *Address) TargetId() string { + return t.GetTargetId() +} + +func (t *Address) Address() string { + return t.GetAddress() +} + +func (t *Address) GetPublicId() string { + return t.GetTargetId() +} + +// allocTargetAddress will allocate a address +func allocTargetAddress() *Address { + return &Address{ + TargetAddress: &store.TargetAddress{}, + } +} diff --git a/internal/target/address_test.go b/internal/target/address_test.go new file mode 100644 index 000000000..25bfbeb89 --- /dev/null +++ b/internal/target/address_test.go @@ -0,0 +1,67 @@ +package target_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddress_New(t *testing.T) { + type args struct { + targetId string + address string + } + tests := []struct { + name string + args args + want *target.Address + wantErr errors.Code + }{ + { + name: "no-target_id", + args: args{ + address: "0.0.0.0", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "no-address", + args: args{ + targetId: "targ_0000000", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "valid", + args: args{ + targetId: "targ_0000000", + address: "0.0.0.0", + }, + want: &target.Address{ + TargetAddress: &store.TargetAddress{ + TargetId: "targ_0000000", + Address: "0.0.0.0", + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := target.NewAddress(tt.args.targetId, tt.args.address) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/target/options.go b/internal/target/options.go index 2f3b44786..5eabe1a4f 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -41,6 +41,7 @@ type options struct { WithEgressWorkerFilter string WithIngressWorkerFilter string WithTargetIds []string + WithAddress string } func getDefaultOptions() options { @@ -64,6 +65,7 @@ func getDefaultOptions() options { WithWorkerFilter: "", WithEgressWorkerFilter: "", WithIngressWorkerFilter: "", + WithAddress: "", } } @@ -207,3 +209,10 @@ func WithPermissions(perms []perms.Permission) Option { o.WithPermissions = perms } } + +// WithAddress provides an optional network address +func WithAddress(address string) Option { + return func(o *options) { + o.WithAddress = address + } +} diff --git a/internal/target/repository.go b/internal/target/repository.go index cd7c50c54..d929b4a42 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -125,6 +125,7 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op target := allocTargetView() target.PublicId = publicIdOrName + var address string var hostSources []HostSource var credSources []CredentialSource _, err := r.writer.DoTx( @@ -150,6 +151,13 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op if credSources, err = fetchCredentialSources(ctx, read, target.PublicId); err != nil { return errors.Wrap(ctx, err, op) } + targetAddress, err := fetchAddress(ctx, read, target.PublicId) + if err != nil && !errors.IsNotFoundError(err) { + return errors.Wrap(ctx, err, op) + } + if targetAddress != nil { + address = targetAddress.GetAddress() + } return nil }, ) @@ -159,7 +167,7 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op } return nil, nil, nil, errors.Wrap(ctx, err, op) } - subtype, err := target.targetSubtype(ctx) + subtype, err := target.targetSubtype(ctx, address) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op) } @@ -242,9 +250,28 @@ func (r *Repository) ListTargets(ctx context.Context, opt ...Option) ([]Target, return nil, errors.Wrap(ctx, err, op) } + var targetIds []string + for _, t := range foundTargets { + targetIds = append(targetIds, t.GetPublicId()) + } + + addresses := map[string]string{} + var foundAddresses []*Address + err = r.reader.SearchWhere(ctx, &foundAddresses, "target_id in (?)", []any{targetIds}) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + for _, addr := range foundAddresses { + addresses[addr.TargetId()] = addr.Address() + } + targets := make([]Target, 0, len(foundTargets)) for _, t := range foundTargets { - subtype, err := t.targetSubtype(ctx) + var address string + if v, ok := addresses[t.GetPublicId()]; ok { + address = v + } + subtype, err := t.targetSubtype(ctx, address) if err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -336,80 +363,6 @@ func (r *Repository) DeleteTarget(ctx context.Context, publicId string, _ ...Opt return rowsDeleted, nil } -// update a target in the db repository with an oplog entry. -// It currently supports no options. -func (r *Repository) update(ctx context.Context, target Target, version uint32, fieldMaskPaths []string, setToNullPaths []string, _ ...Option) (Target, []HostSource, []CredentialSource, int, error) { - const op = "target.(Repository).update" - if version == 0 { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") - } - if target == nil { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "nil target") - } - cloner, ok := target.(Cloneable) - if !ok { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "target is not cloneable") - } - dbOpts := []db.Option{ - db.WithVersion(&version), - } - projectId := target.GetProjectId() - if projectId == "" { - t := allocTargetView() - t.PublicId = target.GetPublicId() - if err := r.reader.LookupByPublicId(ctx, &t); err != nil { - return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("lookup failed for %s", t.PublicId))) - } - projectId = t.ProjectId - } - oplogWrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeOplog) - if err != nil { - return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) - } - metadata := target.Oplog(oplog.OpType_OP_TYPE_UPDATE) - dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata)) - - var rowsUpdated int - var returnedTarget any - var hostSources []HostSource - var credSources []CredentialSource - _, 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 { - return errors.Wrap(ctx, err, op) - } - if rowsUpdated > 1 { - // return err, which will result in a rollback of the update - return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") - } - - if hostSources, err = fetchHostSources(ctx, reader, target.GetPublicId()); err != nil { - return errors.Wrap(ctx, err, op) - } - - if credSources, err = fetchCredentialSources(ctx, reader, target.GetPublicId()); err != nil { - return errors.Wrap(ctx, err, op) - } - return nil - }, - ) - if err != nil { - return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) - } - return returnedTarget.(Target), hostSources, credSources, rowsUpdated, nil -} - // CreateTarget inserts into the repository and returns the new Target with // its list of host sets and credential libraries. // WithPublicId is the only supported option. @@ -455,6 +408,15 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt t.SetPublicId(ctx, id) } + var address *Address + var err error + if t.GetAddress() != "" { + address, err = NewAddress(t.GetPublicId(), t.GetAddress()) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op) + } + } + oplogWrapper, err := r.kms.GetWrapper(ctx, target.GetProjectId(), kms.KeyPurposeOplog) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -480,6 +442,15 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target")) } msgs = append(msgs, &targetOplogMsg) + + if address != nil { + var targetAddressOplogMsg oplog.Message + if err := w.Create(ctx, address, db.NewOplogMsg(&targetAddressOplogMsg)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target address")) + } + msgs = append(msgs, &targetAddressOplogMsg) + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) } @@ -504,6 +475,15 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui if target == nil { return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target") } + if target.GetPublicId() == "" { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target public id") + } + if target.GetProjectId() == "" { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing project id") + } + if version == 0 { + return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } vet, ok := subtypeRegistry.vetForUpdateFunc(target.GetType()) if !ok { return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType())) @@ -512,10 +492,7 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui return nil, nil, nil, db.NoRowsAffected, err } - if target.GetPublicId() == "" { - return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target public id") - } - + var addressEndpoint string for _, f := range fieldMaskPaths { switch { case strings.EqualFold("name", f): @@ -526,10 +503,13 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui case strings.EqualFold("workerfilter", f): case strings.EqualFold("egressworkerfilter", f): case strings.EqualFold("ingressworkerfilter", f): + case strings.EqualFold("address", f): + addressEndpoint = target.GetAddress() default: return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) } } + var dbMask, nullFields []string dbMask, nullFields = dbw.BuildUpdatePaths( map[string]any{ @@ -541,6 +521,7 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui "WorkerFilter": target.GetWorkerFilter(), "EgressWorkerFilter": target.GetEgressWorkerFilter(), "IngressWorkerFilter": target.GetIngressWorkerFilter(), + "Address": target.GetAddress(), }, fieldMaskPaths, []string{"SessionMaxSeconds", "SessionConnectionLimit"}, @@ -548,21 +529,110 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui if len(dbMask) == 0 && len(nullFields) == 0 { return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "empty field mask") } - var returnedTarget Target + + // The Address field is not apart of the target schema in the database. + // It is apart of a different table called target_address, which is why + // the Address field must be filtered out of the dbMask & nullFields slices. + var updateAddress, deleteAddress bool + var filteredDbMask, filteredNullFields []string + for _, f := range dbMask { + switch { + case strings.EqualFold("Address", f): + updateAddress = true + default: + filteredDbMask = append(filteredDbMask, f) + } + } + for _, f := range nullFields { + switch { + case strings.EqualFold("Address", f): + deleteAddress = true + default: + filteredNullFields = append(filteredNullFields, f) + } + } + + // If the Address field is the only present change, then we must still + // update the target's version because a target address is a child object + // of the target. + if (len(filteredDbMask) == 0 && len(filteredNullFields) == 0) && (updateAddress || deleteAddress) { + target.SetVersion(version + 1) + filteredDbMask = append(filteredDbMask, "Version") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, target.GetProjectId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + var rowsUpdated int + var returnedTarget Target var hostSources []HostSource var credSources []CredentialSource - _, err := r.writer.DoTx( + _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(read db.Reader, w db.Writer) error { - var err error t := target.Clone() - returnedTarget, hostSources, credSources, rowsUpdated, err = r.update(ctx, t, version, dbMask, nullFields) + rowsUpdated, err = w.Update(ctx, t, filteredDbMask, filteredNullFields, + db.WithOplog(oplogWrapper, t.Oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithVersion(&version), + ) if err != nil { return errors.Wrap(ctx, err, op) } + if rowsUpdated > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 target resource would have been updated") + } + + if hostSources, err = fetchHostSources(ctx, read, t.GetPublicId()); err != nil { + return errors.Wrap(ctx, err, op) + } + if credSources, err = fetchCredentialSources(ctx, read, t.GetPublicId()); err != nil { + return errors.Wrap(ctx, err, op) + } + + var address *Address + switch { + case updateAddress: + if len(hostSources) > 0 { + return errors.New(ctx, errors.Conflict, op, "unable to set address because one or more host sources is assigned to the given target") + } + address, err = NewAddress(t.GetPublicId(), addressEndpoint) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if err := w.Create(ctx, address, + db.WithOplog(oplogWrapper, address.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithOnConflict(&db.OnConflict{ + Target: db.Columns{"target_id"}, + Action: db.UpdateAll(true), + })); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target address")) + } + case deleteAddress: + var err error + address = allocTargetAddress() + address.TargetAddress.TargetId = t.GetPublicId() + rowsUpdated, err = w.Delete(ctx, address, db.WithOplog(oplogWrapper, address.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target address")) + } + if rowsUpdated > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 target resource would have been deleted") + } + default: + address, err = fetchAddress(ctx, read, t.GetPublicId()) + if err != nil && !errors.IsNotFoundError(err) { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to fetch target address")) + } + } + if address != nil { + t.SetAddress(address.GetAddress()) + } + returnedTarget = t.Clone() + return nil }, ) diff --git a/internal/target/repository_address.go b/internal/target/repository_address.go new file mode 100644 index 000000000..85f2cadbf --- /dev/null +++ b/internal/target/repository_address.go @@ -0,0 +1,21 @@ +package target + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" +) + +func fetchAddress(ctx context.Context, r db.Reader, targetId string) (*Address, error) { + const op = "target.fetchAddress" + var address *Address + if err := r.SearchWhere(ctx, &address, "target_id = ?", []any{targetId}); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if address.TargetAddress == nil { + return nil, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("%s target address not found", targetId), errors.WithoutEvent()) + } + return address, nil +} diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index fac23638c..8331b7738 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -60,7 +60,7 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st var hostSources []HostSource var credSources []CredentialSource - var updatedTarget any + var updatedTarget Target _, err = r.writer.DoTx( ctx, db.StdRetryCnt, @@ -72,7 +72,7 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) } - updatedTarget = target.(Cloneable).Clone() + updatedTarget = target.Clone() var targetOplogMsg oplog.Message rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) if err != nil { @@ -121,6 +121,13 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve credential sources after adding")) } + address, err := fetchAddress(ctx, reader, targetId) + if err != nil && !errors.IsNotFoundError(err) { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve target address after adding")) + } + if address != nil { + updatedTarget.SetAddress(address.GetAddress()) + } return nil }, ) diff --git a/internal/target/repository_host_source.go b/internal/target/repository_host_source.go index 338431ed2..56d600cd1 100644 --- a/internal/target/repository_host_source.go +++ b/internal/target/repository_host_source.go @@ -58,18 +58,27 @@ func (r *Repository) AddTargetHostSources(ctx context.Context, targetId string, } var currentHostSources []HostSource var currentCredSources []CredentialSource - var updatedTarget any + var updatedTarget Target _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { + address, err := fetchAddress(ctx, reader, targetId) + if err != nil && !errors.IsNotFoundError(err) { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current target address")) + } + if address != nil && address.GetAddress() != "" { + return errors.New(ctx, errors.Conflict, op, "unable to add host sources because a network address is directly assigned to the given target") + } + msgs := make([]*oplog.Message, 0, 2) targetTicket, err := w.GetTicket(ctx, target) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) } - updatedTarget = target.(Cloneable).Clone() + + updatedTarget = target.Clone() var targetOplogMsg oplog.Message rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) if err != nil { @@ -103,7 +112,8 @@ func (r *Repository) AddTargetHostSources(ctx context.Context, targetId string, if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating sets")) } - return updatedTarget.(Target), currentHostSources, currentCredSources, nil + + return updatedTarget, currentHostSources, currentCredSources, nil } // DeleteTargeHostSources deletes host sources from a target (targetId). The @@ -284,6 +294,14 @@ func (r *Repository) SetTargetHostSources(ctx context.Context, targetId string, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { + address, err := fetchAddress(ctx, reader, targetId) + if err != nil && !errors.IsNotFoundError(err) { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve current target address")) + } + if address != nil && address.GetAddress() != "" { + return errors.New(ctx, errors.Conflict, op, "unable to set host sources because a network address is directly assigned to the given target") + } + msgs := make([]*oplog.Message, 0, 2) targetTicket, err := w.GetTicket(ctx, target) if err != nil { diff --git a/internal/target/store/target.pb.go b/internal/target/store/target.pb.go index 8473e3f74..56fda3e02 100644 --- a/internal/target/store/target.pb.go +++ b/internal/target/store/target.pb.go @@ -8,6 +8,7 @@ package store import ( timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -270,6 +271,65 @@ func (x *TargetHostSet) GetCreateTime() *timestamp.Timestamp { return nil } +type TargetAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // target_id of the Target + // @inject_tag: gorm:"primary_key" + TargetId string `protobuf:"bytes,10,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"primary_key"` + // address associated to the Target + // @inject_tag: `gorm:"not_null"` + Address string `protobuf:"bytes,20,opt,name=address,proto3" json:"address,omitempty" gorm:"not_null"` +} + +func (x *TargetAddress) Reset() { + *x = TargetAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TargetAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetAddress) ProtoMessage() {} + +func (x *TargetAddress) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetAddress.ProtoReflect.Descriptor instead. +func (*TargetAddress) Descriptor() ([]byte, []int) { + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{2} +} + +func (x *TargetAddress) GetTargetId() string { + if x != nil { + return x.TargetId + } + return "" +} + +func (x *TargetAddress) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + type CredentialLibrary struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -292,7 +352,7 @@ type CredentialLibrary struct { func (x *CredentialLibrary) Reset() { *x = CredentialLibrary{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -305,7 +365,7 @@ func (x *CredentialLibrary) String() string { func (*CredentialLibrary) ProtoMessage() {} func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[2] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -318,7 +378,7 @@ func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialLibrary.ProtoReflect.Descriptor instead. func (*CredentialLibrary) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{2} + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{3} } func (x *CredentialLibrary) GetTargetId() string { @@ -371,7 +431,7 @@ type StaticCredential struct { func (x *StaticCredential) Reset() { *x = StaticCredential{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -384,7 +444,7 @@ func (x *StaticCredential) String() string { func (*StaticCredential) ProtoMessage() {} func (x *StaticCredential) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -397,7 +457,7 @@ func (x *StaticCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredential.ProtoReflect.Descriptor instead. func (*StaticCredential) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{3} + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{4} } func (x *StaticCredential) GetTargetId() string { @@ -453,7 +513,7 @@ type CredentialSource struct { func (x *CredentialSource) Reset() { *x = CredentialSource{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +526,7 @@ func (x *CredentialSource) String() string { func (*CredentialSource) ProtoMessage() {} func (x *CredentialSource) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +539,7 @@ func (x *CredentialSource) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialSource.ProtoReflect.Descriptor instead. func (*CredentialSource) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{4} + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{5} } func (x *CredentialSource) GetTargetId() string { @@ -533,7 +593,7 @@ type CredentialSourceView struct { func (x *CredentialSourceView) Reset() { *x = CredentialSourceView{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -546,7 +606,7 @@ func (x *CredentialSourceView) String() string { func (*CredentialSourceView) ProtoMessage() {} func (x *CredentialSourceView) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -559,7 +619,7 @@ func (x *CredentialSourceView) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialSourceView.ProtoReflect.Descriptor instead. func (*CredentialSourceView) Descriptor() ([]byte, []int) { - return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{5} + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{6} } func (x *CredentialSourceView) GetPublicId() string { @@ -584,109 +644,118 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, - 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x04, 0x0a, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xe0, 0x04, 0x0a, 0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x56, 0x69, 0x65, + 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2e, + 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x38, + 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x16, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x31, 0x0a, + 0x14, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x65, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x12, 0x33, 0x0a, 0x15, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x53, + 0x65, 0x74, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x22, 0x5e, 0x0a, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, + 0x30, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x16, 0xc2, 0xdd, 0x29, 0x12, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, - 0x5a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, - 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, - 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x6e, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x12, 0x31, 0x0a, 0x14, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x12, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x15, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x8c, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, - 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, - 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, - 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, - 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, - 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x10, 0x53, 0x74, 0x61, - 0x74, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, - 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, - 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, - 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xf1, 0x01, 0x0a, 0x10, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, - 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, - 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, - 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, - 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x47, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, + 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xf1, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, + 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x47, 0x0a, 0x14, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, + 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -701,23 +770,24 @@ func file_controller_storage_target_store_v1_target_proto_rawDescGZIP() []byte { return file_controller_storage_target_store_v1_target_proto_rawDescData } -var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_controller_storage_target_store_v1_target_proto_goTypes = []interface{}{ (*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView (*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet - (*CredentialLibrary)(nil), // 2: controller.storage.target.store.v1.CredentialLibrary - (*StaticCredential)(nil), // 3: controller.storage.target.store.v1.StaticCredential - (*CredentialSource)(nil), // 4: controller.storage.target.store.v1.CredentialSource - (*CredentialSourceView)(nil), // 5: controller.storage.target.store.v1.CredentialSourceView - (*timestamp.Timestamp)(nil), // 6: controller.storage.timestamp.v1.Timestamp + (*TargetAddress)(nil), // 2: controller.storage.target.store.v1.TargetAddress + (*CredentialLibrary)(nil), // 3: controller.storage.target.store.v1.CredentialLibrary + (*StaticCredential)(nil), // 4: controller.storage.target.store.v1.StaticCredential + (*CredentialSource)(nil), // 5: controller.storage.target.store.v1.CredentialSource + (*CredentialSourceView)(nil), // 6: controller.storage.target.store.v1.CredentialSourceView + (*timestamp.Timestamp)(nil), // 7: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_target_store_v1_target_proto_depIdxs = []int32{ - 6, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // 4: controller.storage.target.store.v1.StaticCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 6, // 5: controller.storage.target.store.v1.CredentialSource.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 4: controller.storage.target.store.v1.StaticCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 7, // 5: controller.storage.target.store.v1.CredentialSource.create_time:type_name -> controller.storage.timestamp.v1.Timestamp 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name @@ -756,7 +826,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { } } file_controller_storage_target_store_v1_target_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CredentialLibrary); i { + switch v := v.(*TargetAddress); i { case 0: return &v.state case 1: @@ -768,7 +838,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { } } file_controller_storage_target_store_v1_target_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StaticCredential); i { + switch v := v.(*CredentialLibrary); i { case 0: return &v.state case 1: @@ -780,7 +850,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { } } file_controller_storage_target_store_v1_target_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CredentialSource); i { + switch v := v.(*StaticCredential); i { case 0: return &v.state case 1: @@ -792,6 +862,18 @@ func file_controller_storage_target_store_v1_target_proto_init() { } } file_controller_storage_target_store_v1_target_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_target_store_v1_target_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CredentialSourceView); i { case 0: return &v.state @@ -810,7 +892,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_target_store_v1_target_proto_rawDesc, NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/target/target.go b/internal/target/target.go index 21ad9f136..50ac8550e 100644 --- a/internal/target/target.go +++ b/internal/target/target.go @@ -28,6 +28,7 @@ type Target interface { GetWorkerFilter() string GetEgressWorkerFilter() string GetIngressWorkerFilter() string + GetAddress() string Clone() Target SetPublicId(context.Context, string) error SetProjectId(string) @@ -42,6 +43,7 @@ type Target interface { SetWorkerFilter(string) SetEgressWorkerFilter(string) SetIngressWorkerFilter(string) + SetAddress(string) Oplog(op oplog.OpType) oplog.Metadata } @@ -55,6 +57,8 @@ var _ boundary.AuthzProtectedEntity = (*targetView)(nil) // underlying type. type targetView struct { *store.TargetView + // Network address assigned to the Target. + Address string `json:"address,omitempty" gorm:"-"` tableName string `gorm:"-"` } @@ -105,7 +109,7 @@ func (t *targetView) Subtype() subtypes.Subtype { } // targetSubtype converts the target view to the concrete subtype -func (t *targetView) targetSubtype(ctx context.Context) (Target, error) { +func (t *targetView) targetSubtype(ctx context.Context, address string) (Target, error) { const op = "target.targetView.targetSubtype" alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) @@ -129,5 +133,6 @@ func (t *targetView) targetSubtype(ctx context.Context) (Target, error) { tt.SetWorkerFilter(t.WorkerFilter) tt.SetEgressWorkerFilter(t.EgressWorkerFilter) tt.SetIngressWorkerFilter(t.IngressWorkerFilter) + tt.SetAddress(address) return tt, nil } diff --git a/internal/target/targettest/target.go b/internal/target/targettest/target.go index 108c5e8cd..160f4a268 100644 --- a/internal/target/targettest/target.go +++ b/internal/target/targettest/target.go @@ -27,6 +27,7 @@ const ( // Target is a target.Target used for tests. type Target struct { *store.Target + Address string `gorm:"-"` tableName string `gorm:"-"` } @@ -125,10 +126,15 @@ func (t *Target) GetIngressWorkerFilter() string { return t.IngressWorkerFilter } +func (t *Target) GetAddress() string { + return t.Address +} + func (t *Target) Clone() target.Target { cp := proto.Clone(t.Target) return &Target{ - Target: cp.(*store.Target), + Address: t.Address, + Target: cp.(*store.Target), } } @@ -185,6 +191,10 @@ func (t *Target) SetIngressWorkerFilter(filter string) { t.IngressWorkerFilter = filter } +func (t *Target) SetAddress(a string) { + t.Address = a +} + func (t *Target) Oplog(op oplog.OpType) oplog.Metadata { return oplog.Metadata{ "resource-public-id": []string{t.PublicId}, diff --git a/internal/target/tcp/exports_test.go b/internal/target/tcp/exports_test.go index 5c405983c..08b8ac5aa 100644 --- a/internal/target/tcp/exports_test.go +++ b/internal/target/tcp/exports_test.go @@ -1,6 +1,9 @@ package tcp -import "github.com/hashicorp/boundary/internal/target" +import ( + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" +) // Expose functions and variables for tests. var ( @@ -17,3 +20,12 @@ func NewTestTarget(projectId string, opt ...target.Option) target.Target { t.SetProjectId(projectId) return t } + +// NewTestAddress is a test helper that bypasses the targetId & address checks +// performed by NewAddress, allowing tests to create a target Address with +// nil fields for more robust testing. +func NewTestAddress() *target.Address { + return &target.Address{ + TargetAddress: &store.TargetAddress{}, + } +} diff --git a/internal/target/tcp/repository_host_source_test.go b/internal/target/tcp/repository_host_source_test.go index 09c60c936..d85fecfd6 100644 --- a/internal/target/tcp/repository_host_source_test.go +++ b/internal/target/tcp/repository_host_source_test.go @@ -50,13 +50,15 @@ func TestRepository_AddTargetHostSets(t *testing.T) { type args struct { targetVersion uint32 wantTargetIds bool + wantAddress bool opt []target.Option } tests := []struct { - name string - args args - wantErr bool - wantErrIs error + name string + args args + wantErr bool + wantErrMsg string + wantErrIs error }{ { name: "valid", @@ -66,6 +68,16 @@ func TestRepository_AddTargetHostSets(t *testing.T) { }, wantErr: false, }, + { + name: "address-mutually-exclusive-relationship", + args: args{ + targetVersion: 1, + wantAddress: true, + wantTargetIds: true, + }, + wantErr: true, + wantErrMsg: "unable to add host sources because a network address is directly assigned to the given target", + }, { name: "bad-version", args: args{ @@ -99,11 +111,20 @@ func TestRepository_AddTargetHostSets(t *testing.T) { ctx := context.Background() projTarget := tcp.TestTarget(ctx, t, conn, staticProj.PublicId, "static-proj") + var address *target.Address + if tt.args.wantAddress { + address = target.TestTargetAddress(t, conn, projTarget.GetPublicId(), "8.8.8.8") + } + var hostSourceIds []string origTarget, origHostSet, _, err := repo.LookupTarget(ctx, projTarget.GetPublicId()) require.NoError(err) require.Equal(0, len(origHostSet)) + if address != nil { + require.Equal(address.GetAddress(), origTarget.GetAddress()) + } + if tt.args.wantTargetIds { hostSourceIds = createHostSetsFn([]string{staticProj.PublicId}) } @@ -114,6 +135,9 @@ func TestRepository_AddTargetHostSets(t *testing.T) { if tt.wantErrIs != nil { assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) } + if tt.wantErrMsg != "" { + assert.Contains(err.Error(), tt.wantErrMsg) + } // test to see of the target version update oplog was not created err = db.TestVerifyOplog(t, rw, projTarget.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.Error(err) @@ -448,6 +472,7 @@ func TestRepository_SetTargetHostSets(t *testing.T) { setup func(target.Target) []target.HostSource args args wantAffectedRows int + wantErrMsg string wantErr bool }{ { @@ -461,6 +486,17 @@ func TestRepository_SetTargetHostSets(t *testing.T) { wantErr: false, wantAffectedRows: 10, }, + { + name: "address-mutually-exclusive-relationship", + args: args{ + target: tcp.TestTarget(ctx, t, conn, proj.PublicId, "invalid-host-source", target.WithAddress("8.8.8.8")), + targetVersion: 2, // yep, since setupFn will increment it to 2 + hostSourceIds: []string{testHostSetIds[0], testHostSetIds[1]}, + addToOrigHostSources: true, + }, + wantErr: true, + wantErrMsg: "unable to set host sources because a network address is directly assigned to the given target", + }, { name: "no-change", setup: setupFn, diff --git a/internal/target/tcp/repository_tcp_target_test.go b/internal/target/tcp/repository_tcp_target_test.go index 6dd757e3f..919ae895f 100644 --- a/internal/target/tcp/repository_tcp_target_test.go +++ b/internal/target/tcp/repository_tcp_target_test.go @@ -64,6 +64,21 @@ func TestRepository_CreateTarget(t *testing.T) { }, wantErr: false, }, + { + name: "with-address", + args: args{ + target: func() target.Target { + target, err := target.New(ctx, tcp.Subtype, proj.PublicId, + target.WithName("with-address"), + target.WithDescription("with-address"), + target.WithDefaultPort(80), + target.WithAddress("8.8.8.8")) + require.NoError(t, err) + return target + }(), + }, + wantErr: false, + }, { name: "nil-target", args: args{ @@ -187,6 +202,7 @@ func TestRepository_CreateTarget(t *testing.T) { foundTarget, foundHostSources, foundCredLibs, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) assert.NoError(err) + assert.Equal(tt.args.target.GetAddress(), tar.GetAddress()) assert.True(proto.Equal(tar.(*tcp.Target), foundTarget.(*tcp.Target))) assert.Equal(hostSources, foundHostSources) assert.Equal(credSources, foundCredLibs) @@ -224,6 +240,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { type args struct { name string description string + address string port uint32 fieldMaskPaths []string opt []target.Option @@ -231,16 +248,17 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { PublicId *string } tests := []struct { - name string - newProjectId string - newName string - newTargetOpts []target.Option - args args - wantRowsUpdate int - wantErr bool - wantErrMsg string - wantIsError errors.Code - wantDup bool + name string + newProjectId string + newName string + newTargetOpts []target.Option + args args + wantRowsUpdate int + wantErr bool + wantErrMsg string + wantIsError errors.Code + wantDup bool + wantHostSources bool }{ { name: "valid", @@ -249,9 +267,63 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Name"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: false, - wantRowsUpdate: 1, + newProjectId: proj.PublicId, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: true, + }, + + { + name: "valid-address", + args: args{ + name: "valid-address" + id, + fieldMaskPaths: []string{"Name", "Address"}, + ProjectId: proj.PublicId, + address: "8.8.8.8", + }, + newProjectId: proj.PublicId, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: false, + }, + { + name: "null-address", + args: args{ + fieldMaskPaths: []string{"Address"}, + ProjectId: proj.PublicId, + address: "null", + }, + newProjectId: proj.PublicId, + newTargetOpts: []target.Option{target.WithAddress("8.8.8.8")}, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: false, + }, + { + name: "delete-address", + args: args{ + fieldMaskPaths: []string{"Address"}, + ProjectId: proj.PublicId, + }, + newProjectId: proj.PublicId, + newTargetOpts: []target.Option{target.WithAddress("8.8.8.8")}, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: false, + }, + { + name: "host-source-mutually-exclusive-relationship", + args: args{ + name: "invalid-address" + id, + address: "8.8.8.8", + fieldMaskPaths: []string{"Name", "Address"}, + ProjectId: proj.PublicId, + }, + newProjectId: proj.PublicId, + wantErr: true, + wantErrMsg: "unable to set address because one or more host sources is assigned to the given target", + wantRowsUpdate: 0, + wantHostSources: true, }, { name: "valid-no-op", @@ -260,10 +332,11 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Name"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - newName: "valid-no-op" + id, - wantErr: false, - wantRowsUpdate: 1, + newProjectId: proj.PublicId, + newName: "valid-no-op" + id, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: true, }, { name: "not-found", @@ -273,11 +346,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { ProjectId: proj.PublicId, PublicId: func() *string { s := "1"; return &s }(), }, - newProjectId: proj.PublicId, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "record not found, search issue: error #1100", - wantIsError: errors.RecordNotFound, + newProjectId: proj.PublicId, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "record not found, search issue: error #1100", + wantIsError: errors.RecordNotFound, + wantHostSources: true, }, { name: "null-name", @@ -286,11 +360,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Name"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - newName: "null-name" + id, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "db.DoTx: target.(Repository).UpdateTarget: target.(Repository).update: db.DoTx: target.(Repository).update: db.Update: name must not be empty: not null constraint violated: integrity violation: error #1001", + newProjectId: proj.PublicId, + newName: "null-name" + id, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "db.DoTx: target.(Repository).UpdateTarget: db.Update: name must not be empty: not null constraint violated: integrity violation: error #1001", + wantHostSources: true, }, { name: "null-description", @@ -299,10 +374,11 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Description"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - newTargetOpts: []target.Option{target.WithDescription("null-description" + id)}, - wantErr: false, - wantRowsUpdate: 1, + newProjectId: proj.PublicId, + newTargetOpts: []target.Option{target.WithDescription("null-description" + id)}, + wantErr: false, + wantRowsUpdate: 1, + wantHostSources: true, }, { name: "empty-field-mask", @@ -311,11 +387,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", - wantIsError: errors.EmptyFieldMask, + newProjectId: proj.PublicId, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", + wantIsError: errors.EmptyFieldMask, + wantHostSources: true, }, { name: "nil-fieldmask", @@ -324,11 +401,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: nil, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", - wantIsError: errors.EmptyFieldMask, + newProjectId: proj.PublicId, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", + wantIsError: errors.EmptyFieldMask, + wantHostSources: true, }, { name: "read-only-fields", @@ -337,11 +415,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"CreateTime"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: CreateTime: parameter violation: error #103", - wantIsError: errors.InvalidFieldMask, + newProjectId: proj.PublicId, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: CreateTime: parameter violation: error #103", + wantIsError: errors.InvalidFieldMask, + wantHostSources: true, }, { name: "unknown-fields", @@ -350,11 +429,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Alice"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantRowsUpdate: 0, - wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: Alice: parameter violation: error #103", - wantIsError: errors.InvalidFieldMask, + newProjectId: proj.PublicId, + wantErr: true, + wantRowsUpdate: 0, + wantErrMsg: "target.(Repository).UpdateTarget: invalid field mask: Alice: parameter violation: error #103", + wantIsError: errors.InvalidFieldMask, + wantHostSources: true, }, { name: "no-public-id", @@ -364,11 +444,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { ProjectId: proj.PublicId, PublicId: pubId(""), }, - newProjectId: proj.PublicId, - wantErr: true, - wantErrMsg: "target.(Repository).UpdateTarget: missing target public id: parameter violation: error #100", - wantIsError: errors.InvalidParameter, - wantRowsUpdate: 0, + newProjectId: proj.PublicId, + wantErr: true, + wantErrMsg: "target.(Repository).UpdateTarget: missing target public id: parameter violation: error #100", + wantIsError: errors.InvalidParameter, + wantRowsUpdate: 0, + wantHostSources: true, }, { name: "project-id-no-mask", @@ -376,21 +457,11 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { name: "project-id" + id, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", - wantIsError: errors.EmptyFieldMask, - }, - { - name: "empty-project-id-with-name-mask", - args: args{ - name: "empty-project-id" + id, - fieldMaskPaths: []string{"Name"}, - ProjectId: "", - }, - newProjectId: proj.PublicId, - wantErr: false, - wantRowsUpdate: 1, + newProjectId: proj.PublicId, + wantErr: true, + wantErrMsg: "target.(Repository).UpdateTarget: empty field mask: parameter violation: error #104", + wantIsError: errors.EmptyFieldMask, + wantHostSources: true, }, { name: "dup-name", @@ -399,11 +470,12 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { fieldMaskPaths: []string{"Name"}, ProjectId: proj.PublicId, }, - newProjectId: proj.PublicId, - wantErr: true, - wantDup: true, - wantErrMsg: " already exists in project " + proj.PublicId, - wantIsError: errors.NotUnique, + newProjectId: proj.PublicId, + wantErr: true, + wantDup: true, + wantErrMsg: " already exists in project " + proj.PublicId, + wantIsError: errors.NotUnique, + wantHostSources: true, }, } css := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) @@ -416,11 +488,18 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { _ = tcp.TestTarget(ctx, t, conn, proj.PublicId, tt.args.name) } - testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) - hsets := static.TestSets(t, conn, testCats[0].GetPublicId(), 5) - testHostSetIds := make([]string, 0, len(hsets)) - for _, hs := range hsets { - testHostSetIds = append(testHostSetIds, hs.PublicId) + testHostSetIds := []string{} + if tt.wantHostSources { + testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) + hsets := static.TestSets(t, conn, testCats[0].GetPublicId(), 5) + testHostSetIds = make([]string, 0, len(hsets)) + for _, hs := range hsets { + testHostSetIds = append(testHostSetIds, hs.PublicId) + } + tt.newTargetOpts = append( + tt.newTargetOpts, + target.WithHostSources(testHostSetIds), + ) } cls := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 5) @@ -438,7 +517,6 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { tt.newTargetOpts = append( tt.newTargetOpts, - target.WithHostSources(testHostSetIds), target.WithCredentialLibraries(testCredLibs), ) name := tt.newName @@ -451,6 +529,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { target.WithName(tt.args.name), target.WithDescription(tt.args.description), target.WithDefaultPort(tt.args.port), + target.WithAddress(tt.args.address), ) updateTarget.SetPublicId(ctx, tar.GetPublicId()) if tt.args.PublicId != nil { @@ -464,6 +543,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { assert.True(errors.Match(errors.T(tt.wantIsError), err)) assert.Nil(targetAfterUpdate) assert.Equal(0, updatedRows) + assert.NotEmpty(err.Error()) assert.Contains(err.Error(), tt.wantErrMsg) err = db.TestVerifyOplog(t, rw, tar.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) assert.Error(err) @@ -478,6 +558,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { afterUpdateIds = append(afterUpdateIds, hs.Id()) } assert.Equal(testHostSetIds, afterUpdateIds) + assert.Equal(tt.args.address, targetAfterUpdate.GetAddress()) afterUpdateIds = make([]string, 0, len(credSources)) for _, cl := range credSources { @@ -487,13 +568,14 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { switch tt.name { case "valid-no-op": - assert.Equal(tar.GetUpdateTime(), targetAfterUpdate.GetUpdateTime()) + assert.Equal(tar.GetUpdateTime().String(), targetAfterUpdate.GetUpdateTime().String()) default: assert.NotEqual(tar.GetUpdateTime(), targetAfterUpdate.GetUpdateTime()) } foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) assert.NoError(err) assert.True(proto.Equal(targetAfterUpdate.((*tcp.Target)), foundTarget.((*tcp.Target)))) + assert.Equal(targetAfterUpdate.GetAddress(), foundTarget.GetAddress()) underlyingDB, err := conn.SqlDB(ctx) require.NoError(err) dbassert := dbassert.New(t, underlyingDB) diff --git a/internal/target/tcp/target.go b/internal/target/tcp/target.go index fff99710b..b75d032ff 100644 --- a/internal/target/tcp/target.go +++ b/internal/target/tcp/target.go @@ -27,6 +27,8 @@ const ( // that can be accessed via TCP. It is a subtype of target.Target. type Target struct { *store.Target + // Network address assigned to the Target. + Address string `json:"address,omitempty" gorm:"-"` tableName string `gorm:"-"` } @@ -57,6 +59,7 @@ func (h targetHooks) NewTarget(projectId string, opt ...target.Option) (target.T EgressWorkerFilter: opts.WithEgressWorkerFilter, IngressWorkerFilter: opts.WithIngressWorkerFilter, }, + Address: opts.WithAddress, } return t, nil } @@ -72,7 +75,8 @@ func (h targetHooks) AllocTarget() target.Target { func (t *Target) Clone() target.Target { cp := proto.Clone(t.Target) return &Target{ - Target: cp.(*store.Target), + Target: cp.(*store.Target), + Address: t.Address, } } @@ -124,6 +128,10 @@ func (t *Target) GetType() subtypes.Subtype { return Subtype } +func (t *Target) GetAddress() string { + return t.Address +} + func (t *Target) SetPublicId(ctx context.Context, publicId string) error { const op = "tcp.(Target).SetPublicId" if !strings.HasPrefix(publicId, TargetPrefix+"_") { @@ -181,3 +189,7 @@ func (t *Target) SetEgressWorkerFilter(filter string) { func (t *Target) SetIngressWorkerFilter(filter string) { t.IngressWorkerFilter = filter } + +func (t *Target) SetAddress(address string) { + t.Address = address +} diff --git a/internal/target/tcp/target_test.go b/internal/target/tcp/target_test.go index c8c3b7c09..160d08dc4 100644 --- a/internal/target/tcp/target_test.go +++ b/internal/target/tcp/target_test.go @@ -324,7 +324,9 @@ func TestTarget_Clone(t *testing.T) { t.Run("valid", func(t *testing.T) { assert := assert.New(t) _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - tar := tcp.TestTarget(ctx, t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId)) + tar := tcp.TestTarget(ctx, t, conn, proj.PublicId, tcp.TestTargetName(t, proj.PublicId), + target.WithAddress("8.8.8.8"), + ) cp := tar.Clone() assert.True(proto.Equal(cp.(*tcp.Target).Target, tar.(*tcp.Target).Target)) }) diff --git a/internal/target/tcp/testing.go b/internal/target/tcp/testing.go index f127a4bc1..c7c527b11 100644 --- a/internal/target/tcp/testing.go +++ b/internal/target/tcp/testing.go @@ -26,6 +26,13 @@ func TestTarget(ctx context.Context, t testing.TB, conn *db.DB, projectId, name err = rw.Create(context.Background(), tar) require.NoError(err) + if opts.WithAddress != "" { + address, err := target.NewAddress(tar.GetPublicId(), opts.WithAddress) + require.NoError(err) + require.NotNil(address) + err = rw.Create(context.Background(), address) + require.NoError(err) + } if len(opts.WithHostSources) > 0 { newHostSets := make([]any, 0, len(opts.WithHostSources)) for _, s := range opts.WithHostSources { diff --git a/internal/target/testing.go b/internal/target/testing.go index ef5b36321..413f9e915 100644 --- a/internal/target/testing.go +++ b/internal/target/testing.go @@ -23,6 +23,17 @@ func TestNewCredentialLibrary(targetId, credentialLibraryId string, purpose cred } } +// TestNewTargetAddress creates a new in memory TargetAddress +// representing the association between a Target and network address. +func TestNewTargetAddress(targetId, address string) *Address { + return &Address{ + TargetAddress: &store.TargetAddress{ + TargetId: targetId, + Address: address, + }, + } +} + // TestNewStaticCredential creates a new in memory StaticCredential // representing the relationship between targetId and credentialId with // the given purpose. @@ -47,3 +58,15 @@ func TestCredentialLibrary(t testing.TB, conn *db.DB, targetId, libraryId string require.NoError(err) return lib } + +// TestTargetAddress creates am association for targetId and +// a given address value. +func TestTargetAddress(t testing.TB, conn *db.DB, targetId, address string) *Address { + t.Helper() + require := require.New(t) + rw := db.New(conn) + staticAddress := TestNewTargetAddress(targetId, address) + err := rw.Create(context.Background(), staticAddress) + require.NoError(err) + return staticAddress +}