diff --git a/api/hosts/host.gen.go b/api/hosts/host.gen.go index 41ac9042d..4a140cce5 100644 --- a/api/hosts/host.gen.go +++ b/api/hosts/host.gen.go @@ -14,15 +14,17 @@ import ( ) type Host struct { - Id string `json:"id,omitempty"` - Scope *scopes.ScopeInfo `json:"scope,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - CreatedTime time.Time `json:"created_time,omitempty"` - UpdatedTime time.Time `json:"updated_time,omitempty"` - Version uint32 `json:"version,omitempty"` - Type string `json:"type,omitempty"` - Address string `json:"address,omitempty"` + Id string `json:"id,omitempty"` + Scope *scopes.ScopeInfo `json:"scope,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + UpdatedTime time.Time `json:"updated_time,omitempty"` + Version uint32 `json:"version,omitempty"` + Type string `json:"type,omitempty"` + HostCatalogId string `json:"host_catalog_id,omitempty"` + HostSetIds []string `json:"host_set_ids,omitempty"` + Attributes map[string]interface{} `json:"attributes,omitempty"` } type HostsClient struct { diff --git a/api/hosts/host_test.go b/api/hosts/host_test.go new file mode 100644 index 000000000..ba30adc31 --- /dev/null +++ b/api/hosts/host_test.go @@ -0,0 +1,116 @@ +package hosts_test + +import ( + "net/http" + "testing" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/servers/controller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHost_Crud(t *testing.T) { + assert, require := assert.New(t), require.New(t) + amId := "paum_1234567890" + tc := controller.NewTestController(t, &controller.TestControllerOpts{ + DisableAuthorizationFailures: true, + DefaultAuthMethodId: amId, + DefaultLoginName: "user", + DefaultPassword: "passpass", + }) + defer tc.Shutdown() + + client := tc.Client() + org, proj := iam.TestScopes(t, tc.IamRepo()) + client.SetScopeId(org.GetPublicId()) + projClient := client.Clone() + projClient.SetScopeId(proj.GetPublicId()) + + hc, apiErr, err := hosts.NewHostCatalogsClient(projClient).Create(tc.Context(), "static") + require.NoError(err) + require.Nil(apiErr) + require.NotNil(hc) + + checkHost := func(step string, h *hosts.Host, apiErr *api.Error, err error, wantedName string, wantVersion uint32) { + require.NoError(err, step) + if !assert.Nil(apiErr, step) && apiErr.Message != "" { + t.Errorf("ApiError message: %q", apiErr.Message) + } + assert.NotNil(h, "returned no resource", step) + gotName := "" + if h.Name != "" { + gotName = h.Name + } + assert.Equal(wantedName, gotName, step) + assert.Equal(wantVersion, h.Version) + } + + hClient := hosts.NewHostsClient(projClient) + + h, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithName("foo"), hosts.WithStaticHostAddress("someaddress")) + checkHost("create", h, apiErr, err, "foo", 1) + + h, apiErr, err = hClient.Read(tc.Context(), hc.Id, h.Id) + checkHost("read", h, apiErr, err, "foo", 1) + + h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hosts.WithName("bar")) + checkHost("update", h, apiErr, err, "bar", 2) + + h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hosts.DefaultName()) + checkHost("update", h, apiErr, err, "", 3) + + existed, apiErr, err := hClient.Delete(tc.Context(), hc.Id, h.Id) + assert.NoError(err) + assert.True(existed, "Expected existing catalog when deleted, but it wasn't.") + + existed, apiErr, err = hClient.Delete(tc.Context(), hc.Id, h.Id) + assert.NoError(err) + assert.False(existed, "Expected catalog to not exist when deleted, but it did.") +} + +// TODO: Get better coverage for expected errors and error formats. +func TestHost_Errors(t *testing.T) { + assert, require := assert.New(t), require.New(t) + amId := "paum_1234567890" + tc := controller.NewTestController(t, &controller.TestControllerOpts{ + DisableAuthorizationFailures: true, + DefaultAuthMethodId: amId, + DefaultLoginName: "user", + DefaultPassword: "passpass", + }) + defer tc.Shutdown() + + client := tc.Client() + _, proj := iam.TestScopes(t, tc.IamRepo()) + client.SetScopeId(proj.GetPublicId()) + + hc, apiErr, err := hosts.NewHostCatalogsClient(client).Create(tc.Context(), "static") + require.NoError(err) + require.Nil(apiErr) + require.NotNil(hc) + + hClient := hosts.NewHostsClient(client) + + h, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithName("foo"), hosts.WithStaticHostAddress("someaddress")) + require.NoError(err) + require.Nil(apiErr) + assert.NotNil(h) + + _, apiErr, err = hClient.Create(tc.Context(), hc.Id, hosts.WithName("foo"), hosts.WithStaticHostAddress("someaddress")) + require.NoError(err) + assert.NotNil(apiErr) + + _, apiErr, err = hClient.Read(tc.Context(), hc.Id, static.HostPrefix+"_doesntexis") + require.NoError(err) + assert.NotNil(apiErr) + assert.EqualValues(apiErr.Status, http.StatusNotFound) + + _, apiErr, err = hClient.Read(tc.Context(), hc.Id, "invalid id") + require.NoError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusForbidden, apiErr.Status) +} diff --git a/api/hosts/option.gen.go b/api/hosts/option.gen.go index 0f013682a..93017c519 100644 --- a/api/hosts/option.gen.go +++ b/api/hosts/option.gen.go @@ -53,15 +53,27 @@ func WithAutomaticVersioning() Option { } } -func WithAddress(inAddress string) Option { +func WithStaticHostAddress(inAddress string) Option { return func(o *options) { - o.valueMap["address"] = inAddress + raw, ok := o.valueMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["address"] = inAddress + o.valueMap["attributes"] = val } } -func DefaultAddress() Option { +func DefaultStaticHostAddress() Option { return func(o *options) { - o.valueMap["address"] = nil + raw, ok := o.valueMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["address"] = nil + o.valueMap["attributes"] = val } } diff --git a/api/hosts/static_host_attributes.gen.go b/api/hosts/static_host_attributes.gen.go new file mode 100644 index 000000000..f16d27136 --- /dev/null +++ b/api/hosts/static_host_attributes.gen.go @@ -0,0 +1,6 @@ +// Code generated by "make api"; DO NOT EDIT. +package hosts + +type StaticHostAttributes struct { + Address string `json:"address,omitempty"` +} diff --git a/api/internal/genapi/input.go b/api/internal/genapi/input.go index 57903076f..7331b7d0f 100644 --- a/api/internal/genapi/input.go +++ b/api/internal/genapi/input.go @@ -258,6 +258,11 @@ var inputStructs = []*structInfo{ pathArgs: []string{"host-catalog", "host"}, versionEnabled: true, }, + { + inProto: &hosts.StaticHostAttributes{}, + outFile: "hosts/static_host_attributes.gen.go", + subtypeName: "StaticHost", + }, { inProto: &hosts.HostSet{}, outFile: "hosts/host_set.gen.go", diff --git a/internal/cmd/commands/hosts/host_create.go b/internal/cmd/commands/hosts/host_create.go index 6a8fc0ba6..06f97dfeb 100644 --- a/internal/cmd/commands/hosts/host_create.go +++ b/internal/cmd/commands/hosts/host_create.go @@ -112,7 +112,7 @@ func (c *CreateCommand) Run(args []string) int { host, apiErr, err := hosts.NewHostsClient(client).Create(c.Context, c.flagCatalogId, - hosts.WithAddress(c.flagAddress), + hosts.WithStaticHostAddress(c.flagAddress), hosts.WithName(c.flagName), hosts.WithDescription(c.flagDescription)) diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 905b1b229..d2b770149 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -1322,13 +1322,6 @@ "$ref": "#/definitions/controller.api.resources.hosts.v1.Host" } }, - { - "name": "version", - "in": "query", - "required": false, - "type": "integer", - "format": "int64" - }, { "name": "update_mask", "in": "query", @@ -2292,16 +2285,27 @@ "version": { "type": "integer", "format": "int64", - "description": "The version can be used in subsequent write requests to ensure this resource\nhas not changed and to fail the write if it has.\nOutput only.", - "readOnly": true + "description": "The version can be used in subsequent write requests to ensure this resource\nhas not changed and to fail the write if it has." }, "type": { "type": "string", "title": "The type of the resource, to help differentiate schemas" }, - "address": { + "host_catalog_id": { "type": "string", - "title": "The address (DNS or IP name) used to reach the host" + "title": "The host catalog this host set is a part of\nOutput only" + }, + "host_set_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of host sets this host is in\nOutput only.", + "readOnly": true + }, + "attributes": { + "type": "object", + "description": "The attributes that are applied for the specific host type." } }, "title": "Host contains all fields related to a Host resource" diff --git a/internal/gen/controller/api/resources/hosts/host.pb.go b/internal/gen/controller/api/resources/hosts/host.pb.go index 7eb6aa76d..79912a0c8 100644 --- a/internal/gen/controller/api/resources/hosts/host.pb.go +++ b/internal/gen/controller/api/resources/hosts/host.pb.go @@ -8,6 +8,7 @@ package hosts import ( proto "github.com/golang/protobuf/proto" + _struct "github.com/golang/protobuf/ptypes/struct" timestamp "github.com/golang/protobuf/ptypes/timestamp" wrappers "github.com/golang/protobuf/ptypes/wrappers" scopes "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" @@ -53,12 +54,17 @@ type Host struct { UpdatedTime *timestamp.Timestamp `protobuf:"bytes,60,opt,name=updated_time,proto3" json:"updated_time,omitempty"` // The version can be used in subsequent write requests to ensure this resource // has not changed and to fail the write if it has. - // Output only. Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty"` // The type of the resource, to help differentiate schemas - Type *wrappers.StringValue `protobuf:"bytes,80,opt,name=type,proto3" json:"type,omitempty"` - // The address (DNS or IP name) used to reach the host - Address *wrappers.StringValue `protobuf:"bytes,90,opt,name=address,proto3" json:"address,omitempty"` + Type string `protobuf:"bytes,80,opt,name=type,proto3" json:"type,omitempty"` + // The host catalog this host set is a part of + // Output only + HostCatalogId string `protobuf:"bytes,90,opt,name=host_catalog_id,proto3" json:"host_catalog_id,omitempty"` + // A list of host sets this host is in + // Output only. + HostSetIds []string `protobuf:"bytes,100,rep,name=host_set_ids,json=hostSetIds,proto3" json:"host_set_ids,omitempty"` + // The attributes that are applied for the specific host type. + Attributes *_struct.Struct `protobuf:"bytes,110,opt,name=attributes,proto3" json:"attributes,omitempty"` } func (x *Host) Reset() { @@ -142,14 +148,76 @@ func (x *Host) GetVersion() uint32 { return 0 } -func (x *Host) GetType() *wrappers.StringValue { +func (x *Host) GetType() string { if x != nil { return x.Type } + return "" +} + +func (x *Host) GetHostCatalogId() string { + if x != nil { + return x.HostCatalogId + } + return "" +} + +func (x *Host) GetHostSetIds() []string { + if x != nil { + return x.HostSetIds + } return nil } -func (x *Host) GetAddress() *wrappers.StringValue { +func (x *Host) GetAttributes() *_struct.Struct { + if x != nil { + return x.Attributes + } + return nil +} + +type StaticHostAttributes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The address (DNS or IP name) used to reach the host + Address *wrappers.StringValue `protobuf:"bytes,10,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *StaticHostAttributes) Reset() { + *x = StaticHostAttributes{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_hosts_v1_host_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StaticHostAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StaticHostAttributes) ProtoMessage() {} + +func (x *StaticHostAttributes) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_hosts_v1_host_proto_msgTypes[1] + 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 StaticHostAttributes.ProtoReflect.Descriptor instead. +func (*StaticHostAttributes) Descriptor() ([]byte, []int) { + return file_controller_api_resources_hosts_v1_host_proto_rawDescGZIP(), []int{1} +} + +func (x *StaticHostAttributes) GetAddress() *wrappers.StringValue { if x != nil { return x.Address } @@ -164,55 +232,68 @@ var file_controller_api_resources_hosts_v1_host_proto_rawDesc = []byte{ 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 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, 0x22, 0x91, - 0x04, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, - 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, - 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x74, 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, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x5a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x42, 0x51, 0x5a, 0x4f, 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, 0x67, 0x65, 0x6e, - 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3b, - 0x68, 0x6f, 0x73, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 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, 0x22, 0xc0, 0x04, 0x0a, + 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x74, 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, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, + 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x20, + 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x49, 0x64, 0x73, + 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x6e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, + 0xda, 0x29, 0x01, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, + 0x75, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x5d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x25, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1d, + 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 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, 0x42, 0x51, 0x5a, 0x4f, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x68, 0x6f, + 0x73, 0x74, 0x73, 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -227,21 +308,23 @@ func file_controller_api_resources_hosts_v1_host_proto_rawDescGZIP() []byte { return file_controller_api_resources_hosts_v1_host_proto_rawDescData } -var file_controller_api_resources_hosts_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_api_resources_hosts_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_controller_api_resources_hosts_v1_host_proto_goTypes = []interface{}{ (*Host)(nil), // 0: controller.api.resources.hosts.v1.Host - (*scopes.ScopeInfo)(nil), // 1: controller.api.resources.scopes.v1.ScopeInfo - (*wrappers.StringValue)(nil), // 2: google.protobuf.StringValue - (*timestamp.Timestamp)(nil), // 3: google.protobuf.Timestamp + (*StaticHostAttributes)(nil), // 1: controller.api.resources.hosts.v1.StaticHostAttributes + (*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo + (*wrappers.StringValue)(nil), // 3: google.protobuf.StringValue + (*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp + (*_struct.Struct)(nil), // 5: google.protobuf.Struct } var file_controller_api_resources_hosts_v1_host_proto_depIdxs = []int32{ - 1, // 0: controller.api.resources.hosts.v1.Host.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 2, // 1: controller.api.resources.hosts.v1.Host.name:type_name -> google.protobuf.StringValue - 2, // 2: controller.api.resources.hosts.v1.Host.description:type_name -> google.protobuf.StringValue - 3, // 3: controller.api.resources.hosts.v1.Host.created_time:type_name -> google.protobuf.Timestamp - 3, // 4: controller.api.resources.hosts.v1.Host.updated_time:type_name -> google.protobuf.Timestamp - 2, // 5: controller.api.resources.hosts.v1.Host.type:type_name -> google.protobuf.StringValue - 2, // 6: controller.api.resources.hosts.v1.Host.address:type_name -> google.protobuf.StringValue + 2, // 0: controller.api.resources.hosts.v1.Host.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 3, // 1: controller.api.resources.hosts.v1.Host.name:type_name -> google.protobuf.StringValue + 3, // 2: controller.api.resources.hosts.v1.Host.description:type_name -> google.protobuf.StringValue + 4, // 3: controller.api.resources.hosts.v1.Host.created_time:type_name -> google.protobuf.Timestamp + 4, // 4: controller.api.resources.hosts.v1.Host.updated_time:type_name -> google.protobuf.Timestamp + 5, // 5: controller.api.resources.hosts.v1.Host.attributes:type_name -> google.protobuf.Struct + 3, // 6: controller.api.resources.hosts.v1.StaticHostAttributes.address:type_name -> google.protobuf.StringValue 7, // [7:7] is the sub-list for method output_type 7, // [7:7] is the sub-list for method input_type 7, // [7:7] is the sub-list for extension type_name @@ -267,6 +350,18 @@ func file_controller_api_resources_hosts_v1_host_proto_init() { return nil } } + file_controller_api_resources_hosts_v1_host_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StaticHostAttributes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -274,7 +369,7 @@ func file_controller_api_resources_hosts_v1_host_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_resources_hosts_v1_host_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 2, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/gen/controller/api/services/host_service.pb.go b/internal/gen/controller/api/services/host_service.pb.go index 68718c4fc..6162163b7 100644 --- a/internal/gen/controller/api/services/host_service.pb.go +++ b/internal/gen/controller/api/services/host_service.pb.go @@ -362,9 +362,8 @@ type UpdateHostRequest struct { HostCatalogId string `protobuf:"bytes,1,opt,name=host_catalog_id,proto3" json:"host_catalog_id,omitempty"` Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Version uint32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` - Item *hosts.Host `protobuf:"bytes,4,opt,name=item,proto3" json:"item,omitempty"` - UpdateMask *field_mask.FieldMask `protobuf:"bytes,5,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` + Item *hosts.Host `protobuf:"bytes,3,opt,name=item,proto3" json:"item,omitempty"` + UpdateMask *field_mask.FieldMask `protobuf:"bytes,4,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` } func (x *UpdateHostRequest) Reset() { @@ -413,13 +412,6 @@ func (x *UpdateHostRequest) GetId() string { return "" } -func (x *UpdateHostRequest) GetVersion() uint32 { - if x != nil { - return x.Version - } - return 0 -} - func (x *UpdateHostRequest) GetItem() *hosts.Host { if x != nil { return x.Item @@ -636,104 +628,102 @@ var file_controller_api_services_v1_host_service_proto_rawDesc = []byte{ 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0xe1, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, + 0x22, 0xc7, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x4d, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, - 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, - 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x32, 0x81, 0x08, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xc0, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x48, 0x6f, - 0x73, 0x74, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, + 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5c, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x3f, 0x12, 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, - 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, - 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, - 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x92, 0x41, 0x14, 0x12, 0x12, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x12, 0xd1, 0x01, 0x0a, 0x09, 0x4c, 0x69, - 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, 0x12, 0x32, 0x2f, 0x76, - 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, - 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, - 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, - 0x92, 0x41, 0x2a, 0x12, 0x28, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x48, 0x6f, - 0x73, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0xcc, 0x01, - 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3b, 0x0a, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x4d, 0x0a, + 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, + 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2e, 0x0a, 0x12, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x32, 0x81, 0x08, 0x0a, + 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xc0, 0x01, 0x0a, + 0x07, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x5c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3f, 0x12, 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, + 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x14, 0x12, 0x12, 0x47, 0x65, 0x74, + 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x12, + 0xd1, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x2c, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, + 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, - 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x40, 0x22, 0x32, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, - 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, - 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, - 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x92, 0x41, 0x16, 0x12, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, - 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x12, 0xca, 0x01, 0x0a, - 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, - 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5d, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x45, 0x32, 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x67, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x34, 0x12, 0x32, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, - 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x0f, 0x12, 0x0d, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x12, 0xbe, 0x01, 0x0a, 0x0a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x39, 0x2a, - 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, - 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, - 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, - 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, 0x0f, 0x12, 0x0d, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x42, 0x4d, 0x5a, 0x4b, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x92, 0x41, 0x2a, 0x12, 0x28, 0x4c, 0x69, 0x73, 0x74, 0x20, + 0x61, 0x6c, 0x6c, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x12, 0xcc, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, + 0x73, 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x5f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x40, 0x22, 0x32, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x16, 0x12, 0x14, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, + 0x73, 0x74, 0x12, 0xca, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, + 0x74, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x5d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x45, 0x32, 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, + 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, + 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x0f, + 0x12, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x12, + 0xbe, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x2d, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x39, 0x2a, 0x37, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, + 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, + 0x0f, 0x12, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, + 0x42, 0x4d, 0x5a, 0x4b, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -947,11 +937,37 @@ const _ = grpc.SupportPackageIsVersion6 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type HostServiceClient interface { - // This retrieves the host specified in the request using the basic view. + // GetHost returns a stored Host if present. The provided request + // must include the host catalog and host ID for the resource being + // retrieved. If any of those IDs are missing, malformed or reference a + // non existing resource an error is returned. GetHost(ctx context.Context, in *GetHostRequest, opts ...grpc.CallOption) (*GetHostResponse, error) + // ListHosts returns a list of stored hosts which exist inside the scope + // referenced inside the request. The request must include the scope ID and + // the host catalog id for the hosts being retrieved. If the any of those + // are missing, malformed, or reference a non existing scope, an error is + // returned. ListHosts(ctx context.Context, in *ListHostsRequest, opts ...grpc.CallOption) (*ListHostsResponse, error) + // CreateHost creates and stores a host in boundary. The provided + // request must include the scope and host catalog ids in which the host + // will be created. If the scope or catalog id is missing, malformed or references a + // non existing resource, an error is returned. If a name is provided that + // is in use by another host in the same host catalog, an error is returned. CreateHost(ctx context.Context, in *CreateHostRequest, opts ...grpc.CallOption) (*CreateHostResponse, error) + // UpdateHost updates an existing host in boundary. The provided + // host must not have any read only fields set. The update mask must be + // included in the request and contain at least 1 mutable field. To unset + // a field's value, include the field in the update mask and don't set it + // in the provided host. An error is returned if either the scope + // or host catalog or host ids are missing or reference a non existing + // resource. An error is also returned if the request attempts to update the + // name to one that is already in use in this host catalog. UpdateHost(ctx context.Context, in *UpdateHostRequest, opts ...grpc.CallOption) (*UpdateHostResponse, error) + // DeleteHost removes a host from Boundary. If the provided scope, + // host catalog, or host ids are malformed or not provided an error is + // returned. No error is returned if any of the ids reference resources that + // do not exist since the response itself specifies if the resource existed + // before the DeleteHost request was received. DeleteHost(ctx context.Context, in *DeleteHostRequest, opts ...grpc.CallOption) (*DeleteHostResponse, error) } @@ -1010,11 +1026,37 @@ func (c *hostServiceClient) DeleteHost(ctx context.Context, in *DeleteHostReques // HostServiceServer is the server API for HostService service. type HostServiceServer interface { - // This retrieves the host specified in the request using the basic view. + // GetHost returns a stored Host if present. The provided request + // must include the host catalog and host ID for the resource being + // retrieved. If any of those IDs are missing, malformed or reference a + // non existing resource an error is returned. GetHost(context.Context, *GetHostRequest) (*GetHostResponse, error) + // ListHosts returns a list of stored hosts which exist inside the scope + // referenced inside the request. The request must include the scope ID and + // the host catalog id for the hosts being retrieved. If the any of those + // are missing, malformed, or reference a non existing scope, an error is + // returned. ListHosts(context.Context, *ListHostsRequest) (*ListHostsResponse, error) + // CreateHost creates and stores a host in boundary. The provided + // request must include the scope and host catalog ids in which the host + // will be created. If the scope or catalog id is missing, malformed or references a + // non existing resource, an error is returned. If a name is provided that + // is in use by another host in the same host catalog, an error is returned. CreateHost(context.Context, *CreateHostRequest) (*CreateHostResponse, error) + // UpdateHost updates an existing host in boundary. The provided + // host must not have any read only fields set. The update mask must be + // included in the request and contain at least 1 mutable field. To unset + // a field's value, include the field in the update mask and don't set it + // in the provided host. An error is returned if either the scope + // or host catalog or host ids are missing or reference a non existing + // resource. An error is also returned if the request attempts to update the + // name to one that is already in use in this host catalog. UpdateHost(context.Context, *UpdateHostRequest) (*UpdateHostResponse, error) + // DeleteHost removes a host from Boundary. If the provided scope, + // host catalog, or host ids are malformed or not provided an error is + // returned. No error is returned if any of the ids reference resources that + // do not exist since the response itself specifies if the resource existed + // before the DeleteHost request was received. DeleteHost(context.Context, *DeleteHostRequest) (*DeleteHostResponse, error) } diff --git a/internal/gen/controller/api/services/role_service.pb.go b/internal/gen/controller/api/services/role_service.pb.go index c78b85d35..a4a5c682a 100644 --- a/internal/gen/controller/api/services/role_service.pb.go +++ b/internal/gen/controller/api/services/role_service.pb.go @@ -1951,11 +1951,11 @@ type RoleServiceClient interface { // ID is missing, malformed, or references a non-existing resource, an // error is returned. It is not an error to have duplicate grants. SetRoleGrants(ctx context.Context, in *SetRoleGrantsRequest, opts ...grpc.CallOption) (*SetRoleGrantsResponse, error) - // RemoveRoleGrants removes the users and/or groups from the specified role. + // RemoveRoleGrants removes the grants from the specified role. // The provided request must include the scope and the role IDs from which the - // users and/or groups will be removed. If either ID is missing, malformed, or - // references a non-existing org, an error is returned. It is not an error to - // have duplicate user/group IDs. + // grants will be removed. If either ID is missing, malformed, or + // references a non-existing scope, an error is returned. It is not an error to + // have duplicate grants. RemoveRoleGrants(ctx context.Context, in *RemoveRoleGrantsRequest, opts ...grpc.CallOption) (*RemoveRoleGrantsResponse, error) } @@ -2127,11 +2127,11 @@ type RoleServiceServer interface { // ID is missing, malformed, or references a non-existing resource, an // error is returned. It is not an error to have duplicate grants. SetRoleGrants(context.Context, *SetRoleGrantsRequest) (*SetRoleGrantsResponse, error) - // RemoveRoleGrants removes the users and/or groups from the specified role. + // RemoveRoleGrants removes the grants from the specified role. // The provided request must include the scope and the role IDs from which the - // users and/or groups will be removed. If either ID is missing, malformed, or - // references a non-existing org, an error is returned. It is not an error to - // have duplicate user/group IDs. + // grants will be removed. If either ID is missing, malformed, or + // references a non-existing scope, an error is returned. It is not an error to + // have duplicate grants. RemoveRoleGrants(context.Context, *RemoveRoleGrantsRequest) (*RemoveRoleGrantsResponse, error) } diff --git a/internal/gen/controller/api/services/user_service.pb.go b/internal/gen/controller/api/services/user_service.pb.go index a5c0d29db..856504be0 100644 --- a/internal/gen/controller/api/services/user_service.pb.go +++ b/internal/gen/controller/api/services/user_service.pb.go @@ -878,26 +878,26 @@ type UserServiceClient interface { // resource an error is returned. GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) // ListUsers returns a list of stored users which exist inside the org - // referenced inside the request. The request must include the org ID for + // referenced inside the request. The request must include the scope ID for // the users being retrieved. If the scope ID is missing, malformed, or // reference a non existing scope, an error is returned. ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) // CreateUser creates and stores a user in boundary. The provided - // request must include the org id in which the user will be created. - // If the org id is missing, malformed or references a non existing - // org, an error is returned. If a name is provided that is in - // use in another user in the same org, an error is returned. + // request must include the scope id in which the user will be created. + // If the scope id is missing, malformed or references a non existing + // scope, an error is returned. If a name is provided that is in + // use in another user in the same scope, an error is returned. CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) // UpdateUser updates an existing user in boundary. The provided // user must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset // a field's value, include the field in the update mask and don't set it - // in the provided user. An error is returned if either the org + // in the provided user. An error is returned if either the scope // or user ids are missing or reference a non existing resource. An error // is also returned if the request attempts to update the name to one that is - // already in use in this org. + // already in use in this scope. UpdateUser(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*UpdateUserResponse, error) - // DeleteUser removes a user from Boundary. If the provided org or user ids + // DeleteUser removes a user from Boundary. If the provided scope or user ids // are malformed or not provided an error is returned. No error is returned // if either ids reference resources that do not exist as the response itself // specifies if the resource existed before the DeleteUser request was @@ -966,26 +966,26 @@ type UserServiceServer interface { // resource an error is returned. GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) // ListUsers returns a list of stored users which exist inside the org - // referenced inside the request. The request must include the org ID for + // referenced inside the request. The request must include the scope ID for // the users being retrieved. If the scope ID is missing, malformed, or // reference a non existing scope, an error is returned. ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) // CreateUser creates and stores a user in boundary. The provided - // request must include the org id in which the user will be created. - // If the org id is missing, malformed or references a non existing - // org, an error is returned. If a name is provided that is in - // use in another user in the same org, an error is returned. + // request must include the scope id in which the user will be created. + // If the scope id is missing, malformed or references a non existing + // scope, an error is returned. If a name is provided that is in + // use in another user in the same scope, an error is returned. CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) // UpdateUser updates an existing user in boundary. The provided // user must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset // a field's value, include the field in the update mask and don't set it - // in the provided user. An error is returned if either the org + // in the provided user. An error is returned if either the scope // or user ids are missing or reference a non existing resource. An error // is also returned if the request attempts to update the name to one that is - // already in use in this org. + // already in use in this scope. UpdateUser(context.Context, *UpdateUserRequest) (*UpdateUserResponse, error) - // DeleteUser removes a user from Boundary. If the provided org or user ids + // DeleteUser removes a user from Boundary. If the provided scope or user ids // are malformed or not provided an error is returned. No error is returned // if either ids reference resources that do not exist as the response itself // specifies if the resource existed before the DeleteUser request was diff --git a/internal/host/static/example_test.go b/internal/host/static/example_test.go index d85682790..0d63d8708 100644 --- a/internal/host/static/example_test.go +++ b/internal/host/static/example_test.go @@ -16,7 +16,7 @@ func ExampleNewHostCatalog() { func ExampleNewHost() { catalogPublicId := "sthc_1234" - host, _ := static.NewHost(catalogPublicId, "127.0.0.1") + host, _ := static.NewHost(catalogPublicId, static.WithAddress("127.0.0.1")) fmt.Println(host.Address) // Output: // 127.0.0.1 diff --git a/internal/host/static/host.go b/internal/host/static/host.go index 8f445a62b..b5aed120c 100644 --- a/internal/host/static/host.go +++ b/internal/host/static/host.go @@ -18,19 +18,16 @@ type Host struct { // NewHost creates a new in memory Host for address assigned to catalogId. // Name and description are the only valid options. All other options are // ignored. -func NewHost(catalogId, address string, opt ...Option) (*Host, error) { +func NewHost(catalogId string, opt ...Option) (*Host, error) { if catalogId == "" { return nil, fmt.Errorf("new: static host: no catalog id: %w", db.ErrInvalidParameter) } - if address == "" { - return nil, fmt.Errorf("new: static host: no address: %w", db.ErrInvalidParameter) - } opts := getOpts(opt...) host := &Host{ Host: &store.Host{ CatalogId: catalogId, - Address: address, + Address: opts.withAddress, Name: opts.withName, Description: opts.withDescription, }, diff --git a/internal/host/static/host_catalog_test.go b/internal/host/static/host_catalog_test.go index 06932f227..d656b3cd9 100644 --- a/internal/host/static/host_catalog_test.go +++ b/internal/host/static/host_catalog_test.go @@ -109,30 +109,9 @@ func TestHostCatalog_New(t *testing.T) { } } -func testCatalogs(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string, count int) []*HostCatalog { - t.Helper() - assert := assert.New(t) - var cats []*HostCatalog - for i := 0; i < count; i++ { - cat, err := NewHostCatalog(scopeId) - assert.NoError(err) - assert.NotNil(cat) - id, err := newHostCatalogId() - assert.NoError(err) - assert.NotEmpty(id) - cat.PublicId = id - - w := db.New(conn) - err2 := w.Create(context.Background(), cat) - assert.NoError(err2) - cats = append(cats, cat) - } - return cats -} - func testCatalog(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string) *HostCatalog { t.Helper() - cats := testCatalogs(t, conn, wrapper, scopeId, 1) + cats := TestCatalogs(t, conn, scopeId, 1) return cats[0] } diff --git a/internal/host/static/host_set_member_test.go b/internal/host/static/host_set_member_test.go index dffab09a4..0272e6a7b 100644 --- a/internal/host/static/host_set_member_test.go +++ b/internal/host/static/host_set_member_test.go @@ -17,17 +17,17 @@ func TestHostSetMember_New(t *testing.T) { wrapper := db.TestWrapper(t) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - cats := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + cats := TestCatalogs(t, conn, prj.PublicId, 2) blueCat := cats[0] blueSets := testSets(t, conn, blueCat.GetPublicId(), 2) - blueHosts := testHosts(t, conn, blueCat.GetPublicId(), 2) + blueHosts := TestHosts(t, conn, blueCat.GetPublicId(), 2) // TODO(mgaffney) 05/2020: // these will be needed when the repository code is done // greenCat := cats[1] // greenSets := testSets(t, conn, greenCat.GetPublicId(), 2) - // greenHosts := testHosts(t, conn, greenCat.GetPublicId(), 2) + // greenHosts := TestHosts(t, conn, greenCat.GetPublicId(), 2) var tests = []struct { name string diff --git a/internal/host/static/host_test.go b/internal/host/static/host_test.go index eb1e3d91e..378975e50 100644 --- a/internal/host/static/host_test.go +++ b/internal/host/static/host_test.go @@ -2,13 +2,11 @@ package static import ( "context" - "fmt" "testing" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/iam" - "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,7 +20,6 @@ func TestHost_New(t *testing.T) { conn.LogMode(false) type args struct { catalogId string - address string opts []Option } @@ -37,7 +34,6 @@ func TestHost_New(t *testing.T) { name: "blank-catalogId", args: args{ catalogId: "", - address: "127.0.0.1", }, want: nil, wantCreateErr: true, @@ -46,16 +42,19 @@ func TestHost_New(t *testing.T) { name: "blank-address", args: args{ catalogId: cat.GetPublicId(), - address: "", }, - want: nil, - wantCreateErr: true, + want: &Host{ + Host: &store.Host{ + CatalogId: cat.GetPublicId(), + }, + }, + wantWriteErr: true, }, { name: "address-to-short", args: args{ catalogId: cat.GetPublicId(), - address: "1234567", + opts: []Option{WithAddress("1234567")}, }, want: &Host{ Host: &store.Host{ @@ -69,7 +68,7 @@ func TestHost_New(t *testing.T) { name: "minimum-address", args: args{ catalogId: cat.GetPublicId(), - address: "12345678", + opts: []Option{WithAddress("12345678")}, }, want: &Host{ Host: &store.Host{ @@ -82,21 +81,20 @@ func TestHost_New(t *testing.T) { name: "valid-no-options", args: args{ catalogId: cat.GetPublicId(), - address: "127.0.0.1", }, want: &Host{ Host: &store.Host{ CatalogId: cat.GetPublicId(), - Address: "127.0.0.1", }, }, + wantWriteErr: true, }, { name: "valid-with-name", args: args{ catalogId: cat.GetPublicId(), - address: "127.0.0.1", opts: []Option{ + WithAddress("127.0.0.1"), WithName("test-name"), }, }, @@ -112,8 +110,8 @@ func TestHost_New(t *testing.T) { name: "valid-with-description", args: args{ catalogId: cat.GetPublicId(), - address: "127.0.0.1", opts: []Option{ + WithAddress("127.0.0.1"), WithDescription("test-description"), }, }, @@ -131,7 +129,7 @@ func TestHost_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got, err := NewHost(tt.args.catalogId, tt.args.address, tt.args.opts...) + got, err := NewHost(tt.args.catalogId, tt.args.opts...) if tt.wantCreateErr { assert.Error(err) assert.Nil(got) @@ -158,29 +156,6 @@ func TestHost_New(t *testing.T) { } } -func testHosts(t *testing.T, conn *gorm.DB, catalogId string, count int) []*Host { - t.Helper() - assert := assert.New(t) - var hosts []*Host - - for i := 0; i < count; i++ { - host, err := NewHost(catalogId, fmt.Sprintf("%s-%d", catalogId, i)) - assert.NoError(err) - assert.NotNil(host) - - id, err := newHostCatalogId() - assert.NoError(err) - assert.NotEmpty(id) - host.PublicId = id - - w := db.New(conn) - err2 := w.Create(context.Background(), host) - assert.NoError(err2) - hosts = append(hosts, host) - } - return hosts -} - func TestHost_SetTableName(t *testing.T) { defaultTableName := "static_host" tests := []struct { diff --git a/internal/host/static/immutable_fields_test.go b/internal/host/static/immutable_fields_test.go index a431e3a63..6dfe7f656 100644 --- a/internal/host/static/immutable_fields_test.go +++ b/internal/host/static/immutable_fields_test.go @@ -87,7 +87,7 @@ func TestStaticHost_ImmutableFields(t *testing.T) { ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}} _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) cat := testCatalog(t, conn, wrapper, prj.PublicId) - hosts := testHosts(t, conn, cat.GetPublicId(), 1) + hosts := TestHosts(t, conn, cat.GetPublicId(), 1) new := hosts[0] @@ -237,7 +237,7 @@ func TestStaticHostSetMember_ImmutableFields(t *testing.T) { _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) cat := testCatalog(t, conn, wrapper, prj.PublicId) sets := testSets(t, conn, cat.GetPublicId(), 1) - hosts := testHosts(t, conn, cat.GetPublicId(), 1) + hosts := TestHosts(t, conn, cat.GetPublicId(), 1) new, err := NewHostSetMember(sets[0].PublicId, hosts[0].PublicId) require.NoError(t, err) diff --git a/internal/host/static/options.go b/internal/host/static/options.go index f8822118e..8497f70d1 100644 --- a/internal/host/static/options.go +++ b/internal/host/static/options.go @@ -17,12 +17,14 @@ type options struct { withName string withDescription string withLimit int + withAddress string } func getDefaultOptions() options { return options{ withDescription: "", withName: "", + withAddress: "", } } @@ -48,3 +50,10 @@ func WithLimit(l int) Option { o.withLimit = l } } + +// WithAddress provides an optional address. +func WithAddress(address string) Option { + return func(o *options) { + o.withAddress = address + } +} diff --git a/internal/host/static/options_test.go b/internal/host/static/options_test.go index 1c3d2e0f6..d02eec0bb 100644 --- a/internal/host/static/options_test.go +++ b/internal/host/static/options_test.go @@ -26,4 +26,10 @@ func Test_GetOpts(t *testing.T) { testOpts.withLimit = 5 assert.Equal(t, opts, testOpts) }) + t.Run("WithAddress", func(t *testing.T) { + opts := getOpts(WithAddress("test")) + testOpts := getDefaultOptions() + testOpts.withAddress = "test" + assert.Equal(t, opts, testOpts) + }) } diff --git a/internal/host/static/repository_host_catalog_test.go b/internal/host/static/repository_host_catalog_test.go index 45d9dd4ae..43029abb2 100644 --- a/internal/host/static/repository_host_catalog_test.go +++ b/internal/host/static/repository_host_catalog_test.go @@ -484,7 +484,7 @@ func TestRepository_UpdateCatalog(t *testing.T) { name := "test-dup-name" _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) - cats := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + cats := TestCatalogs(t, conn, prj.PublicId, 2) c1 := cats[0] c1.Name = name got1, gotCount1, err := repo.UpdateCatalog(context.Background(), c1, 1, []string{"name"}) diff --git a/internal/host/static/repository_host_test.go b/internal/host/static/repository_host_test.go index 9c01d8871..a7817ef57 100644 --- a/internal/host/static/repository_host_test.go +++ b/internal/host/static/repository_host_test.go @@ -26,7 +26,7 @@ func TestRepository_CreateHost(t *testing.T) { kms := kms.TestKms(t, conn, wrapper) iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] var tests = []struct { name string @@ -174,7 +174,7 @@ func TestRepository_CreateHost(t *testing.T) { require.NotNil(repo) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] in := &Host{ Host: &store.Host{ @@ -205,7 +205,7 @@ func TestRepository_CreateHost(t *testing.T) { require.NotNil(repo) _, prj := iam.TestScopes(t, iamRepo) - catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + catalogs := TestCatalogs(t, conn, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] @@ -585,7 +585,7 @@ func TestRepository_UpdateHost(t *testing.T) { require.NotNil(repo) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] tt.orig.CatalogId = catalog.PublicId orig, err := repo.CreateHost(context.Background(), prj.GetPublicId(), tt.orig) @@ -634,8 +634,8 @@ func TestRepository_UpdateHost(t *testing.T) { name := "test-dup-name" _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] - hs := testHosts(t, conn, catalog.PublicId, 2) + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] + hs := TestHosts(t, conn, catalog.PublicId, 2) hA, hB := hs[0], hs[1] @@ -664,7 +664,7 @@ func TestRepository_UpdateHost(t *testing.T) { require.NotNil(repo) _, prj := iam.TestScopes(t, iamRepo) - catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + catalogs := TestCatalogs(t, conn, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] @@ -708,12 +708,12 @@ func TestRepository_UpdateHost(t *testing.T) { require.NotNil(repo) _, prj := iam.TestScopes(t, iamRepo) - catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + catalogs := TestCatalogs(t, conn, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] - hA := testHosts(t, conn, catalogA.PublicId, 1)[0] - hB := testHosts(t, conn, catalogB.PublicId, 1)[0] + hA := TestHosts(t, conn, catalogA.PublicId, 1)[0] + hB := TestHosts(t, conn, catalogB.PublicId, 1)[0] assert.NotEqual(hA.CatalogId, hB.CatalogId) orig := hA.clone() @@ -739,8 +739,8 @@ func TestRepository_LookupHost(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] - host := testHosts(t, conn, catalog.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] + host := TestHosts(t, conn, catalog.PublicId, 1)[0] hostId, err := newHostId() require.NoError(t, err) @@ -792,10 +792,10 @@ func TestRepository_ListHosts(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) - catalogs := testCatalogs(t, conn, wrapper, prj.PublicId, 2) + catalogs := TestCatalogs(t, conn, prj.PublicId, 2) catalogA, catalogB := catalogs[0], catalogs[1] - hosts := testHosts(t, conn, catalogA.PublicId, 3) + hosts := TestHosts(t, conn, catalogA.PublicId, 3) var tests = []struct { name string @@ -851,9 +851,9 @@ func TestRepository_ListHosts_Limits(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] count := 10 - hosts := testHosts(t, conn, catalog.PublicId, count) + hosts := TestHosts(t, conn, catalog.PublicId, count) var tests = []struct { name string @@ -921,8 +921,8 @@ func TestRepository_DeleteHost(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) - catalog := testCatalogs(t, conn, wrapper, prj.PublicId, 1)[0] - host := testHosts(t, conn, catalog.PublicId, 1)[0] + catalog := TestCatalogs(t, conn, prj.PublicId, 1)[0] + host := TestHosts(t, conn, catalog.PublicId, 1)[0] newHostId, err := newHostId() require.NoError(t, err) diff --git a/internal/host/static/store/static.pb.go b/internal/host/static/store/static.pb.go index aed0e28be..53e39b213 100644 --- a/internal/host/static/store/static.pb.go +++ b/internal/host/static/store/static.pb.go @@ -475,7 +475,7 @@ var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{ 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0xc6, 0x02, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, + 0x22, 0x9b, 0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, @@ -487,45 +487,53 @@ var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{ 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, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 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, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, - 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, - 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xaf, 0x02, 0x0a, 0x07, 0x48, 0x6f, - 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x02, 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, 0x03, - 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, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 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, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x5e, 0x0a, 0x0d, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, - 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 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, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, + 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, + 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, + 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, + 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xe1, + 0x02, 0x0a, 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 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, 0x03, 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, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, + 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x5e, 0x0a, 0x0d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, + 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, + 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, + 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 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, 0x68, 0x6f, 0x73, + 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/host/static/testing.go b/internal/host/static/testing.go index e452dc5dc..445a556b7 100644 --- a/internal/host/static/testing.go +++ b/internal/host/static/testing.go @@ -2,6 +2,7 @@ package static import ( "context" + "fmt" "testing" "github.com/hashicorp/boundary/internal/db" @@ -32,3 +33,29 @@ func TestCatalogs(t *testing.T, conn *gorm.DB, scopeId string, count int) []*Hos } return cats } + +// TestHosts creates count number of static hosts to the provided DB +// with the provided catalog id. The catalog must have been created previously. +// If any errors are encountered during the creation of the host, the test will fail. +func TestHosts(t *testing.T, conn *gorm.DB, catalogId string, count int) []*Host { + t.Helper() + assert := assert.New(t) + var hosts []*Host + + for i := 0; i < count; i++ { + host, err := NewHost(catalogId, WithAddress(fmt.Sprintf("%s-%d", catalogId, i))) + assert.NoError(err) + assert.NotNil(host) + + id, err := newHostId() + assert.NoError(err) + assert.NotEmpty(id) + host.PublicId = id + + w := db.New(conn) + err2 := w.Create(context.Background(), host) + assert.NoError(err2) + hosts = append(hosts, host) + } + return hosts +} diff --git a/internal/host/static/testing_test.go b/internal/host/static/testing_test.go index 1d72d3208..535908549 100644 --- a/internal/host/static/testing_test.go +++ b/internal/host/static/testing_test.go @@ -24,3 +24,23 @@ func Test_TestCatalogs(t *testing.T) { assert.NotEmpty(c.GetPublicId()) } } + +func Test_TestHosts(t *testing.T) { + t.Helper() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + require.NotNil(org) + assert.NotEmpty(org.GetPublicId()) + + c := TestCatalogs(t, conn, org.GetPublicId(), 1)[0] + + count := 4 + accounts := TestHosts(t, conn, c.GetPublicId(), count) + assert.Len(accounts, count) + for _, a := range accounts { + assert.NotEmpty(a.GetPublicId()) + } +} diff --git a/internal/proto/local/controller/api/resources/hosts/v1/host.proto b/internal/proto/local/controller/api/resources/hosts/v1/host.proto index 2878b9350..35d6e50de 100644 --- a/internal/proto/local/controller/api/resources/hosts/v1/host.proto +++ b/internal/proto/local/controller/api/resources/hosts/v1/host.proto @@ -4,6 +4,7 @@ package controller.api.resources.hosts.v1; option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts;hosts"; +import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "controller/api/resources/scopes/v1/scope.proto"; @@ -35,12 +36,24 @@ message Host { // The version can be used in subsequent write requests to ensure this resource // has not changed and to fail the write if it has. - // Output only. uint32 version = 70; // The type of the resource, to help differentiate schemas - google.protobuf.StringValue type = 80; + string type = 80; - // The address (DNS or IP name) used to reach the host - google.protobuf.StringValue address = 90 [(custom_options.v1.generate_sdk_option) = true]; + // The host catalog this host set is a part of + // Output only + string host_catalog_id = 90 [json_name="host_catalog_id"]; + + // A list of host sets this host is in + // Output only. + repeated string host_set_ids = 100; + + // The attributes that are applied for the specific host type. + google.protobuf.Struct attributes = 110 [(custom_options.v1.generate_sdk_option) = true]; +} + +message StaticHostAttributes { + // The address (DNS or IP name) used to reach the host + google.protobuf.StringValue address = 10 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"attributes.address" that: "address"}]; } diff --git a/internal/proto/local/controller/api/services/v1/host_service.proto b/internal/proto/local/controller/api/services/v1/host_service.proto index 5b12b3a3e..d7aa631db 100644 --- a/internal/proto/local/controller/api/services/v1/host_service.proto +++ b/internal/proto/local/controller/api/services/v1/host_service.proto @@ -10,7 +10,10 @@ import "google/protobuf/field_mask.proto"; import "controller/api/resources/hosts/v1/host.proto"; service HostService { - // This retrieves the host specified in the request using the basic view. + // GetHost returns a stored Host if present. The provided request + // must include the host catalog and host ID for the resource being + // retrieved. If any of those IDs are missing, malformed or reference a + // non existing resource an error is returned. rpc GetHost(GetHostRequest) returns (GetHostResponse) { option (google.api.http) = { get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/hosts/{id}" @@ -20,6 +23,12 @@ service HostService { summary: "Gets a single Host" }; } + + // ListHosts returns a list of stored hosts which exist inside the scope + // referenced inside the request. The request must include the scope ID and + // the host catalog id for the hosts being retrieved. If the any of those + // are missing, malformed, or reference a non existing scope, an error is + // returned. rpc ListHosts(ListHostsRequest) returns (ListHostsResponse) { option (google.api.http) = { get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/hosts" @@ -28,6 +37,12 @@ service HostService { summary: "List all Hosts for the specified catalog" }; } + + // CreateHost creates and stores a host in boundary. The provided + // request must include the scope and host catalog ids in which the host + // will be created. If the scope or catalog id is missing, malformed or references a + // non existing resource, an error is returned. If a name is provided that + // is in use by another host in the same host catalog, an error is returned. rpc CreateHost(CreateHostRequest) returns (CreateHostResponse) { option (google.api.http) = { post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/hosts" @@ -38,6 +53,15 @@ service HostService { summary: "Create a single Host" }; } + + // UpdateHost updates an existing host in boundary. The provided + // host must not have any read only fields set. The update mask must be + // included in the request and contain at least 1 mutable field. To unset + // a field's value, include the field in the update mask and don't set it + // in the provided host. An error is returned if either the scope + // or host catalog or host ids are missing or reference a non existing + // resource. An error is also returned if the request attempts to update the + // name to one that is already in use in this host catalog. rpc UpdateHost(UpdateHostRequest) returns (UpdateHostResponse) { option (google.api.http) = { patch: "/v1/scopes/*/host-catalogs/{host_catalog_id}/hosts/{id}" @@ -48,6 +72,12 @@ service HostService { summary: "Update a Host" }; } + + // DeleteHost removes a host from Boundary. If the provided scope, + // host catalog, or host ids are malformed or not provided an error is + // returned. No error is returned if any of the ids reference resources that + // do not exist since the response itself specifies if the resource existed + // before the DeleteHost request was received. rpc DeleteHost(DeleteHostRequest) returns (DeleteHostResponse) { option (google.api.http) = { delete: "/v1/scopes/*/host-catalogs/{host_catalog_id}/hosts/{id}" @@ -90,9 +120,8 @@ message CreateHostResponse { message UpdateHostRequest { string host_catalog_id = 1 [json_name="host_catalog_id"]; string id = 2; - uint32 version = 3; - api.resources.hosts.v1.Host item = 4; - google.protobuf.FieldMask update_mask = 5; + api.resources.hosts.v1.Host item = 3; + google.protobuf.FieldMask update_mask = 4; } message UpdateHostResponse { diff --git a/internal/proto/local/controller/api/services/v1/role_service.proto b/internal/proto/local/controller/api/services/v1/role_service.proto index f2a4f6dec..e965b7566 100644 --- a/internal/proto/local/controller/api/services/v1/role_service.proto +++ b/internal/proto/local/controller/api/services/v1/role_service.proto @@ -166,11 +166,11 @@ service RoleService { }; } - // RemoveRoleGrants removes the users and/or groups from the specified role. + // RemoveRoleGrants removes the grants from the specified role. // The provided request must include the scope and the role IDs from which the - // users and/or groups will be removed. If either ID is missing, malformed, or - // references a non-existing org, an error is returned. It is not an error to - // have duplicate user/group IDs. + // grants will be removed. If either ID is missing, malformed, or + // references a non-existing scope, an error is returned. It is not an error to + // have duplicate grants. rpc RemoveRoleGrants(RemoveRoleGrantsRequest) returns (RemoveRoleGrantsResponse) { option (google.api.http) = { post: "/v1/scopes/*/roles/{role_id}:remove-grants" diff --git a/internal/proto/local/controller/api/services/v1/user_service.proto b/internal/proto/local/controller/api/services/v1/user_service.proto index 7938ad93a..f68210717 100644 --- a/internal/proto/local/controller/api/services/v1/user_service.proto +++ b/internal/proto/local/controller/api/services/v1/user_service.proto @@ -26,7 +26,7 @@ service UserService { } // ListUsers returns a list of stored users which exist inside the org - // referenced inside the request. The request must include the org ID for + // referenced inside the request. The request must include the scope ID for // the users being retrieved. If the scope ID is missing, malformed, or // reference a non existing scope, an error is returned. rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) { @@ -39,10 +39,10 @@ service UserService { } // CreateUser creates and stores a user in boundary. The provided - // request must include the org id in which the user will be created. - // If the org id is missing, malformed or references a non existing - // org, an error is returned. If a name is provided that is in - // use in another user in the same org, an error is returned. + // request must include the scope id in which the user will be created. + // If the scope id is missing, malformed or references a non existing + // scope, an error is returned. If a name is provided that is in + // use in another user in the same scope, an error is returned. rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { option (google.api.http) = { post: "/v1/scopes/*/users" @@ -58,10 +58,10 @@ service UserService { // user must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset // a field's value, include the field in the update mask and don't set it - // in the provided user. An error is returned if either the org + // in the provided user. An error is returned if either the scope // or user ids are missing or reference a non existing resource. An error // is also returned if the request attempts to update the name to one that is - // already in use in this org. + // already in use in this scope. rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse) { option (google.api.http) = { patch: "/v1/scopes/*/users/{id}" @@ -73,7 +73,7 @@ service UserService { }; } - // DeleteUser removes a user from Boundary. If the provided org or user ids + // DeleteUser removes a user from Boundary. If the provided scope or user ids // are malformed or not provided an error is returned. No error is returned // if either ids reference resources that do not exist as the response itself // specifies if the resource existed before the DeleteUser request was diff --git a/internal/proto/local/controller/storage/host/static/store/v1/static.proto b/internal/proto/local/controller/storage/host/static/store/v1/static.proto index e0fe2623b..13895f7b5 100644 --- a/internal/proto/local/controller/storage/host/static/store/v1/static.proto +++ b/internal/proto/local/controller/storage/host/static/store/v1/static.proto @@ -54,11 +54,11 @@ message Host { // name is optional. If set, it must be unique within // catalog_id. // @inject_tag: `gorm:"default:null"` - string name = 4; + string name = 4 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; // description is optional. // @inject_tag: `gorm:"default:null"` - string description = 5; + string description = 5 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}]; // catalog_id is the public_id of the owning // static_host_catalog and must be set. @@ -68,7 +68,7 @@ message Host { // address is the IP Address or DNS name of the host. It must be set and // it must be unique within catalog_id. // @inject_tag: `gorm:"default:null"` - string address = 7; + string address = 7 [(custom_options.v1.mask_mapping) = {this:"address" that: "attributes.address"}]; // version allows optimistic locking of the resource // @inject_tag: `gorm:"default:null"` @@ -91,11 +91,11 @@ message HostSet { // name is optional. If set, it must be unique within // catalog_id. // @inject_tag: `gorm:"default:null"` - string name = 4; + string name = 4 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}]; // description is optional. // @inject_tag: `gorm:"default:null"` - string description = 5; + string description = 5 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}]; // catalog_id is the public_id of the owning // static_host_catalog and must be set. diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 19f1ad4c3..bffb9e0fd 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens" "github.com/hashicorp/boundary/internal/servers/controller/handlers/groups" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts" "github.com/hashicorp/boundary/internal/servers/controller/handlers/roles" "github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes" "github.com/hashicorp/boundary/internal/servers/controller/handlers/users" @@ -83,6 +84,13 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er if err := services.RegisterHostCatalogServiceHandlerServer(ctx, mux, hcs); err != nil { return nil, fmt.Errorf("failed to register host catalog service handler: %w", err) } + hs, err := hosts.NewService(c.StaticHostRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create host handler service: %w", err) + } + if err := services.RegisterHostServiceHandlerServer(ctx, mux, hs); err != nil { + return nil, fmt.Errorf("failed to register host service handler: %w", err) + } accts, err := accounts.NewService(c.PasswordAuthRepoFn) if err != nil { return nil, fmt.Errorf("failed to create account handler service: %w", err) diff --git a/internal/servers/controller/handler_test.go b/internal/servers/controller/handler_test.go index 387d16a2a..b94e654b5 100644 --- a/internal/servers/controller/handler_test.go +++ b/internal/servers/controller/handler_test.go @@ -59,6 +59,8 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/scopes/someid/groups/someid", "v1/scopes/someid/host-catalogs", "v1/scopes/someid/host-catalogs/someid", + "v1/scopes/someid/host-catalogs/someid/hosts", + "v1/scopes/someid/host-catalogs/someid/hosts/someid", "v1/scopes/someid/roles", "v1/scopes/someid/roles/someid", "v1/scopes/someid/users", @@ -73,6 +75,7 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/scopes/someid/auth-methods", "v1/scopes/someid/auth-methods/someid/accounts", "v1/scopes/someid/host-catalogs", + "v1/scopes/someid/host-catalogs/someid/hosts", // custom methods "v1/scopes/someid/auth-methods/someid:authenticate", @@ -97,6 +100,7 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/scopes/someid/auth-methods/someid", "v1/scopes/someid/auth-methods/someid/accounts/someid", "v1/scopes/someid/host-catalogs/someid", + "v1/scopes/someid/host-catalogs/someid/hosts/someid", }, "PATCH": { "v1/scopes/someid", @@ -105,6 +109,7 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/scopes/someid/groups/someid", "v1/scopes/someid/auth-methods/someid", "v1/scopes/someid/host-catalogs/someid", + "v1/scopes/someid/host-catalogs/someid/hosts/someid", }, } { for _, p := range paths { diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go new file mode 100644 index 000000000..9b844e999 --- /dev/null +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -0,0 +1,436 @@ +package hosts + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/boundary/internal/auth" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/host/static/store" + "github.com/hashicorp/boundary/internal/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +var ( + maskManager handlers.MaskManager + reInvalidID = regexp.MustCompile("[^A-Za-z0-9]") +) + +func init() { + var err error + if maskManager, err = handlers.NewMaskManager(&store.Host{}, &pb.Host{}, &pb.StaticHostAttributes{}); err != nil { + panic(err) + } +} + +type Service struct { + staticRepoFn common.StaticRepoFactory +} + +var _ pbs.HostServiceServer = Service{} + +// NewService returns a host catalog Service which handles host catalog related requests to boundary and uses the provided +// repositories for storage and retrieval. +func NewService(repoFn common.StaticRepoFactory) (Service, error) { + if repoFn == nil { + return Service{}, fmt.Errorf("nil static repository provided") + } + return Service{staticRepoFn: repoFn}, nil +} + +func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs.ListHostsResponse, error) { + authResults := auth.Verify(ctx) + if authResults.Error != nil { + return nil, authResults.Error + } + if err := validateListRequest(req); err != nil { + return nil, err + } + hl, err := s.listFromRepo(ctx, req.GetHostCatalogId()) + if err != nil { + return nil, err + } + for _, item := range hl { + item.Scope = authResults.Scope + } + return &pbs.ListHostsResponse{Items: hl}, nil +} + +// GetHost implements the interface pbs.HostServiceServer. +func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.GetHostResponse, error) { + authResults := auth.Verify(ctx) + if authResults.Error != nil { + return nil, authResults.Error + } + ct := host.SubtypeFromId(req.GetId()) + if ct == host.UnknownSubtype { + return nil, handlers.InvalidArgumentErrorf("Invalid argument provided.", map[string]string{"id": "Improperly formatted identifier used."}) + } + if err := validateGetRequest(req); err != nil { + return nil, err + } + hc, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + hc.Scope = authResults.Scope + return &pbs.GetHostResponse{Item: hc}, nil +} + +// CreateHost implements the interface pbs.HostServiceServer. +func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*pbs.CreateHostResponse, error) { + authResults := auth.Verify(ctx) + if authResults.Error != nil { + return nil, authResults.Error + } + if err := validateCreateRequest(req); err != nil { + return nil, err + } + h, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetHostCatalogId(), req.GetItem()) + if err != nil { + return nil, err + } + h.Scope = authResults.Scope + return &pbs.CreateHostResponse{ + Item: h, + Uri: fmt.Sprintf("scopes/%s/host-catalogs/%s/hosts/%s", authResults.Scope.GetId(), req.GetHostCatalogId(), h.GetId()), + }, nil +} + +// UpdateHost implements the interface pbs.HostServiceServer. +func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*pbs.UpdateHostResponse, error) { + authResults := auth.Verify(ctx) + if authResults.Error != nil { + return nil, authResults.Error + } + ct := host.SubtypeFromId(req.GetId()) + if ct == host.UnknownSubtype { + return nil, handlers.InvalidArgumentErrorf("Invalid argument provided.", map[string]string{"id": "Improperly formatted identifier used."}) + } + if err := validateUpdateRequest(req); err != nil { + return nil, err + } + hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetHostCatalogId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + if err != nil { + return nil, err + } + hc.Scope = authResults.Scope + return &pbs.UpdateHostResponse{Item: hc}, nil +} + +// DeleteHost implements the interface pbs.HostServiceServer. +func (s Service) DeleteHost(ctx context.Context, req *pbs.DeleteHostRequest) (*pbs.DeleteHostResponse, error) { + authResults := auth.Verify(ctx) + if authResults.Error != nil { + return nil, authResults.Error + } + if err := validateDeleteRequest(req); err != nil { + return nil, err + } + existed, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId()) + if err != nil { + return nil, err + } + return &pbs.DeleteHostResponse{Existed: existed}, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Host, error) { + repo, err := s.staticRepoFn() + if err != nil { + return nil, err + } + h, err := repo.LookupHost(ctx, id) + if err != nil { + return nil, err + } + if h == nil { + return nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id) + } + return toProto(h, nil) +} + +func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, item *pb.Host) (*pb.Host, error) { + ha := &pb.StaticHostAttributes{} + if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil { + return nil, status.Errorf(codes.Internal, "Failed converting attributes to subtype proto: %s", err) + } + var opts []static.Option + if ha.GetAddress() != nil { + opts = append(opts, static.WithAddress(ha.GetAddress().GetValue())) + } + if item.GetName() != nil { + opts = append(opts, static.WithName(item.GetName().GetValue())) + } + if item.GetDescription() != nil { + opts = append(opts, static.WithDescription(item.GetDescription().GetValue())) + } + h, err := static.NewHost(catalogId, opts...) + if err != nil { + return nil, status.Errorf(codes.Internal, "Unable to build host for creation: %v.", err) + } + + repo, err := s.staticRepoFn() + if err != nil { + return nil, err + } + out, err := repo.CreateHost(ctx, scopeId, h) + if err != nil { + return nil, status.Errorf(codes.Internal, "Unable to create host: %v.", err) + } + if out == nil { + return nil, status.Error(codes.Internal, "Unable to create host but no error returned from repository.") + } + return toProto(out, nil) +} + +func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string, mask []string, item *pb.Host) (*pb.Host, error) { + ha := &pb.StaticHostAttributes{} + if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil { + return nil, status.Errorf(codes.Internal, "Failed converting attributes to subtype proto: %s", err) + } + var opts []static.Option + if desc := item.GetDescription(); desc != nil { + opts = append(opts, static.WithDescription(desc.GetValue())) + } + if name := item.GetName(); name != nil { + opts = append(opts, static.WithName(name.GetValue())) + } + if addr := ha.GetAddress(); addr != nil { + opts = append(opts, static.WithAddress(addr.GetValue())) + } + h, err := static.NewHost(catalogId, opts...) + if err != nil { + return nil, status.Errorf(codes.Internal, "Unable to build host for update: %v.", err) + } + h.PublicId = id + dbMask := maskManager.Translate(mask) + if len(dbMask) == 0 { + return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."}) + } + repo, err := s.staticRepoFn() + if err != nil { + return nil, err + } + out, rowsUpdated, err := repo.UpdateHost(ctx, scopeId, h, item.GetVersion(), dbMask) + if err != nil { + return nil, status.Errorf(codes.Internal, "Unable to update host: %v.", err) + } + if rowsUpdated == 0 { + return nil, handlers.NotFoundErrorf("Host %q doesn't exist.", id) + } + return toProto(out, nil) +} + +func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { + repo, err := s.staticRepoFn() + if err != nil { + return false, err + } + rows, err := repo.DeleteHost(ctx, scopeId, id) + if err != nil { + return false, status.Errorf(codes.Internal, "Unable to delete host: %v.", err) + } + return rows > 0, nil +} + +func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]*pb.Host, error) { + repo, err := s.staticRepoFn() + if err != nil { + return nil, err + } + hl, err := repo.ListHosts(ctx, catalogId) + if err != nil { + return nil, err + } + var outHl []*pb.Host + for _, h := range hl { + p, err := toProto(h, nil) + if err != nil { + return nil, err + } + outHl = append(outHl, p) + } + return outHl, nil +} + +func toProto(in *static.Host, members []*static.HostSetMember) (*pb.Host, error) { + out := pb.Host{ + Id: in.GetPublicId(), + HostCatalogId: in.GetCatalogId(), + Type: host.StaticSubtype.String(), + CreatedTime: in.GetCreateTime().GetTimestamp(), + UpdatedTime: in.GetUpdateTime().GetTimestamp(), + Version: in.GetVersion(), + } + if in.GetDescription() != "" { + out.Description = wrapperspb.String(in.GetDescription()) + } + if in.GetName() != "" { + out.Name = wrapperspb.String(in.GetName()) + } + for _, m := range members { + out.HostSetIds = append(out.HostSetIds, m.GetSetId()) + } + st, err := handlers.ProtoToStruct(&pb.StaticHostAttributes{Address: wrapperspb.String(in.GetAddress())}) + if err != nil { + return nil, status.Errorf(codes.Internal, "Unable to convert static attribute to struct: %s", err) + } + out.Attributes = st + return &out, nil +} + +// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce +// requirements on the structure of the request. They verify that: +// * The path passed in is correctly formatted +// * All required parameters are set +// * There are no conflicting parameters provided +// * The type asserted by the ID and/or field is known +// * If relevant, the type derived from the id prefix matches what is claimed by the type field +func validateGetRequest(req *pbs.GetHostRequest) error { + badFields := map[string]string{} + if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") { + badFields["host_catalog_id"] = "The field is incorrectly formatted." + } + if !validId(req.GetId(), static.HostPrefix+"_") { + badFields["id"] = "Invalid formatted identifier." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields) + } + return nil +} + +func validateCreateRequest(req *pbs.CreateHostRequest) error { + badFields := map[string]string{} + if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") { + badFields["host_catalog_id"] = "The field is incorrectly formatted." + } + item := req.GetItem() + if item == nil { + return handlers.InvalidArgumentErrorf("Invalid arguments provided.", map[string]string{"item": "this field is required."}) + } + + ha := &pb.StaticHostAttributes{} + if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil { + badFields["attributes"] = "Incorrectly formatted attribute for this type." + } + + if ha.GetAddress() == nil { + badFields["attributes.address"] = "This field is required." + } + + switch host.SubtypeFromId(req.GetHostCatalogId()) { + case host.StaticSubtype: + if item.GetType() != "" && item.GetType() != host.StaticSubtype.String() { + badFields["type"] = "Doesn't match the parent resource's type." + } + attrs := &pb.StaticHostAttributes{} + if err := handlers.StructToProto(item.GetAttributes(), attrs); err != nil { + badFields["attributes"] = "Attribute fields do not match the expected format." + } + if attrs.GetAddress() == nil { + badFields["attributes.address"] = "This is a required field for this type." + } + } + if item.GetId() != "" { + badFields["id"] = "This field is read only." + } + if item.GetHostCatalogId() != "" { + badFields["host_catalog_id"] = "This field is read only." + } + if item.GetCreatedTime() != nil { + badFields["created_time"] = "This field is read only." + } + if item.GetUpdatedTime() != nil { + badFields["updated_time"] = "This field is read only." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields) + } + return nil +} + +func validateUpdateRequest(req *pbs.UpdateHostRequest) error { + badFields := map[string]string{} + if !validId(req.GetId(), static.HostPrefix+"_") { + badFields["id"] = "The field is incorrectly formatted." + } + if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") { + badFields["host_catalog_id"] = "The field is incorrectly formatted." + } + + if req.GetUpdateMask() == nil { + badFields["update_mask"] = "This field is required." + } + + item := req.GetItem() + if item == nil { + // It is legitimate for no item to be specified in an update request as it indicates all fields provided in + // the mask will be marked as unset. + return nil + } + if item.GetVersion() == 0 { + badFields["version"] = "Existing resource version is required for an update." + } + if item.GetType() != "" { + badFields["type"] = "This is a read only field and cannot be specified in an update request." + } + if item.GetHostCatalogId() != "" { + badFields["host_catalog_id"] = "This is a read only field and cannot be specified in an update request." + } + if item.GetId() != "" { + badFields["id"] = "This is a read only field and cannot be specified in an update request." + } + if item.GetCreatedTime() != nil { + badFields["created_time"] = "This is a read only field and cannot be specified in an update request." + } + if item.GetUpdatedTime() != nil { + badFields["updated_time"] = "This is a read only field and cannot be specified in an update request." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + + return nil +} + +func validateListRequest(req *pbs.ListHostsRequest) error { + badFields := map[string]string{} + if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") { + badFields["host_catalog_id"] = "The field is incorrectly formatted." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} + +func validateDeleteRequest(req *pbs.DeleteHostRequest) error { + badFields := map[string]string{} + if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") { + badFields["host_catalog_id"] = "The field is incorrectly formatted." + } + if !validId(req.GetId(), static.HostPrefix+"_") { + badFields["id"] = "The field is incorrectly formatted." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields) + } + return nil +} + +func validId(id, prefix string) bool { + if !strings.HasPrefix(id, prefix) { + return false + } + id = strings.TrimPrefix(id, prefix) + return !reInvalidID.Match([]byte(id)) +} diff --git a/internal/servers/controller/handlers/hosts/host_service_test.go b/internal/servers/controller/handlers/hosts/host_service_test.go new file mode 100644 index 000000000..11bdf906c --- /dev/null +++ b/internal/servers/controller/handlers/hosts/host_service_test.go @@ -0,0 +1,778 @@ +package hosts_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/wrappers" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/db" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts" + "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/genproto/protobuf/field_mask" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" +) + +// TODO: Uncomment all the valid test cases. +func TestGet(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + + toMerge := &pbs.GetHostRequest{ + HostCatalogId: hc.GetPublicId(), + } + + pHost := &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + CreatedTime: h.CreateTime.GetTimestamp(), + UpdatedTime: h.UpdateTime.GetTimestamp(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue(h.GetAddress()), + }}, + } + + cases := []struct { + name string + req *pbs.GetHostRequest + res *pbs.GetHostResponse + errCode codes.Code + }{ + { + name: "Get an Existing Host", + req: &pbs.GetHostRequest{Id: h.GetPublicId()}, + res: &pbs.GetHostResponse{Item: pHost}, + errCode: codes.OK, + }, + { + name: "Get a non existing Host Set", + req: &pbs.GetHostRequest{Id: static.HostPrefix + "_DoesntExis"}, + res: nil, + errCode: codes.NotFound, + }, + { + name: "Wrong id prefix", + req: &pbs.GetHostRequest{Id: "j_1234567890"}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "space in id", + req: &pbs.GetHostRequest{Id: static.HostPrefix + "_1 23456789"}, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + req := proto.Clone(toMerge).(*pbs.GetHostRequest) + proto.Merge(req, tc.req) + + s, err := hosts.NewService(repoFn) + require.NoError(t, err, "Couldn't create a new host set service.") + + got, gErr := s.GetHost(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req) + assert.Equal(tc.errCode, status.Code(gErr), "GetHost(%+v) got error %v, wanted %v", req, gErr, tc.errCode) + + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "GetHost(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestList(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hcs := static.TestCatalogs(t, conn, proj.GetPublicId(), 2) + hc, hcNoHosts := hcs[0], hcs[1] + + var wantHs []*pb.Host + for _, h := range static.TestHosts(t, conn, hc.GetPublicId(), 10) { + wantHs = append(wantHs, &pb.Host{ + Id: h.GetPublicId(), + HostCatalogId: h.GetCatalogId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + UpdatedTime: h.GetUpdateTime().GetTimestamp(), + Version: h.GetVersion(), + Type: host.StaticSubtype.String(), Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue(h.GetAddress()), + }}, + }) + } + + cases := []struct { + name string + hostCatalogId string + res *pbs.ListHostsResponse + errCode codes.Code + }{ + { + name: "List Many Hosts", + hostCatalogId: hc.GetPublicId(), + res: &pbs.ListHostsResponse{Items: wantHs}, + errCode: codes.OK, + }, + { + name: "List No Hosts", + hostCatalogId: hcNoHosts.GetPublicId(), + res: &pbs.ListHostsResponse{}, + errCode: codes.OK, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + s, err := hosts.NewService(repoFn) + require.NoError(err, "Couldn't create new host set service.") + + got, gErr := s.ListHosts(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), &pbs.ListHostsRequest{HostCatalogId: tc.hostCatalogId}) + assert.Equal(tc.errCode, status.Code(gErr), "ListHosts(%q) got error %v, wanted %v", tc.hostCatalogId, gErr, tc.errCode) + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListHosts(%q) got response %q, wanted %q", tc.hostCatalogId, got, tc.res) + }) + } +} + +func TestDelete(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + + s, err := hosts.NewService(repoFn) + require.NoError(t, err, "Couldn't create a new host set service.") + + cases := []struct { + name string + scopeId string + req *pbs.DeleteHostRequest + res *pbs.DeleteHostResponse + errCode codes.Code + }{ + { + name: "Delete an Existing Host", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostRequest{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + }, + res: &pbs.DeleteHostResponse{ + Existed: true, + }, + errCode: codes.OK, + }, + { + name: "Delete bad id Host", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostRequest{ + HostCatalogId: hc.GetPublicId(), + Id: static.HostPrefix + "_doesntexis", + }, + res: &pbs.DeleteHostResponse{ + Existed: false, + }, + errCode: codes.OK, + }, + { + name: "Delete bad host catalog id in Host", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostRequest{ + HostCatalogId: static.HostCatalogPrefix + "_doesntexis", + Id: h.GetPublicId(), + }, + res: &pbs.DeleteHostResponse{ + Existed: false, + }, + errCode: codes.OK, + }, + { + name: "Bad Host Id formatting", + scopeId: proj.GetPublicId(), + req: &pbs.DeleteHostRequest{ + HostCatalogId: hc.GetPublicId(), + Id: static.HostPrefix + "_bad_format", + }, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + got, gErr := s.DeleteHost(auth.DisabledAuthTestContext(auth.WithScopeId(tc.scopeId)), tc.req) + assert.Equal(tc.errCode, status.Code(gErr), "DeleteHost(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode) + assert.Empty(cmp.Diff(tc.res, got, protocmp.Transform()), "DeleteHost(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestDelete_twice(t *testing.T) { + t.Parallel() + assert := assert.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + + s, err := hosts.NewService(repoFn) + require.NoError(t, err, "Couldn't create a new host set service.") + req := &pbs.DeleteHostRequest{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + } + ctx := auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())) + got, gErr := s.DeleteHost(ctx, req) + assert.NoError(gErr, "First attempt") + assert.True(got.GetExisted(), "Expected existed to be true for the first delete.") + got, gErr = s.DeleteHost(ctx, req) + assert.NoError(gErr, "Second attempt") + assert.False(got.GetExisted(), "Expected existed to be false for the second delete.") +} + +func TestCreate(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + + toMerge := &pbs.CreateHostRequest{ + HostCatalogId: hc.GetPublicId(), + } + + defaultHcCreated, err := ptypes.Timestamp(hc.GetCreateTime().GetTimestamp()) + require.NoError(t, err) + + cases := []struct { + name string + req *pbs.CreateHostRequest + res *pbs.CreateHostResponse + errCode codes.Code + }{ + { + name: "Create a valid Host", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("123.456.789"), + }}, + }}, + res: &pbs.CreateHostResponse{ + Uri: fmt.Sprintf("scopes/%s/host-catalogs/%s/hosts/%s_", proj.GetPublicId(), hc.GetPublicId(), static.HostPrefix), + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("123.456.789"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Create without address", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{}}, + }}, + errCode: codes.InvalidArgument, + }, + { + name: "Create with unknown type", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "ThisIsMadeUp", + }}, + errCode: codes.InvalidArgument, + }, + { + name: "Create with no type", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + }}, + errCode: codes.InvalidArgument, + }, + { + name: "Can't specify Id", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + Id: "not allowed to be set", + }}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + CreatedTime: ptypes.TimestampNow(), + }}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateHostRequest{Item: &pb.Host{ + UpdatedTime: ptypes.TimestampNow(), + }}, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + req := proto.Clone(toMerge).(*pbs.CreateHostRequest) + proto.Merge(req, tc.req) + + s, err := hosts.NewService(repoFn) + require.NoError(err, "Failed to create a new host set service.") + + got, gErr := s.CreateHost(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req) + assert.Equal(tc.errCode, status.Code(gErr), "CreateHost(%+v) got error %v, wanted %v", req, gErr, tc.errCode) + if got != nil { + assert.Contains(got.GetUri(), tc.res.GetUri()) + assert.True(strings.HasPrefix(got.GetItem().GetId(), static.HostPrefix)) + gotCreateTime, err := ptypes.Timestamp(got.GetItem().GetCreatedTime()) + require.NoError(err, "Error converting proto to timestamp.") + gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime()) + require.NoError(err, "Error converting proto to timestamp") + // Verify it is a set created after the test setup's default set + assert.True(gotCreateTime.After(defaultHcCreated), "New set should have been created after default set. Was created %v, which is after %v", gotCreateTime, defaultHcCreated) + assert.True(gotUpdateTime.After(defaultHcCreated), "New set should have been updated after default set. Was updated %v, which is after %v", gotUpdateTime, defaultHcCreated) + + // Clear all values which are hard to compare against. + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + } + if tc.res != nil { + tc.res.Item.Version = 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "CreateHost(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +} + +func TestUpdate(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + rw := db.New(conn) + repoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + repo, err := repoFn() + require.NoError(t, err, "Couldn't create new static repo.") + + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + + h, err := static.NewHost(hc.GetPublicId(), static.WithName("default"), static.WithDescription("default"), static.WithAddress("defaultaddress")) + require.NoError(t, err) + h, err = repo.CreateHost(context.Background(), proj.GetPublicId(), h) + require.NoError(t, err) + + var version uint32 = 1 + + resetHost := func() { + version++ + _, _, err = repo.UpdateHost(context.Background(), proj.GetPublicId(), h, version, []string{"Name", "Description", "Address"}) + require.NoError(t, err, "Failed to reset host.") + version++ + } + + hCreated, err := ptypes.Timestamp(h.GetCreateTime().GetTimestamp()) + require.NoError(t, err, "Failed to convert proto to timestamp") + toMerge := &pbs.UpdateHostRequest{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + } + + tested, err := hosts.NewService(repoFn) + require.NoError(t, err, "Failed to create a new host set service.") + + cases := []struct { + name string + req *pbs.UpdateHostRequest + res *pbs.UpdateHostResponse + errCode codes.Code + }{ + { + name: "Update an Existing Host", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name", "description"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Multiple Paths in single string", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name,description"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "No Update Mask", + req: &pbs.UpdateHostRequest{ + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Empty Path", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{Paths: []string{}}, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Only non-existant paths in Mask", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistant_field"}}, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Unset Name", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Host{ + Description: &wrappers.StringValue{Value: "ignored"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Description: &wrappers.StringValue{Value: "default"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Unset Description", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"description"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "ignored"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "default"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Update Only Name", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "updated"}, + Description: &wrappers.StringValue{Value: "ignored"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "updated"}, + Description: &wrappers.StringValue{Value: "default"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Update Only Description", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"description"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "ignored"}, + Description: &wrappers.StringValue{Value: "notignored"}, + }, + }, + res: &pbs.UpdateHostResponse{ + Item: &pb.Host{ + HostCatalogId: hc.GetPublicId(), + Id: h.GetPublicId(), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "default"}, + Description: &wrappers.StringValue{Value: "notignored"}, + CreatedTime: h.GetCreateTime().GetTimestamp(), + Type: "static", + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "address": structpb.NewStringValue("defaultaddress"), + }}, + }, + }, + errCode: codes.OK, + }, + { + name: "Update a Non Existing Host", + req: &pbs.UpdateHostRequest{ + Id: static.HostPrefix + "_DoesntExis", + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"description"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "new"}, + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Description: &wrappers.StringValue{Value: "desc"}, + }, + }, + errCode: codes.Internal, + }, + { + name: "Cant change Id", + req: &pbs.UpdateHostRequest{ + Id: hc.GetPublicId(), + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"id"}, + }, + Item: &pb.Host{ + Id: "p_somethinge", + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String()}, + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "new desc"}, + }}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "Cant specify Created Time", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"created_time"}, + }, + Item: &pb.Host{ + CreatedTime: ptypes.TimestampNow(), + }, + }, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "Cant specify Updated Time", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"updated_time"}, + }, + Item: &pb.Host{ + UpdatedTime: ptypes.TimestampNow(), + }, + }, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "Valid mask, cant specify type", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Host{ + Type: "Unknown", + }, + }, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc.req.Item.Version = version + + req := proto.Clone(toMerge).(*pbs.UpdateHostRequest) + proto.Merge(req, tc.req) + + // Test some bad versions + req.Item.Version = version + 2 + _, gErr := tested.UpdateHost(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req) + require.Error(gErr) + req.Item.Version = version - 1 + _, gErr = tested.UpdateHost(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req) + require.Error(gErr) + req.Item.Version = version + + got, gErr := tested.UpdateHost(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req) + assert.Equal(tc.errCode, status.Code(gErr), "UpdateHost(%+v) got error %v, wanted %v", req, gErr, tc.errCode) + + if tc.errCode == codes.OK { + defer resetHost() + } + + if got != nil { + assert.NotNilf(tc.res, "Expected UpdateHost response to be nil, but was %v", got) + gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime()) + require.NoError(err, "Failed to convert proto to timestamp") + // Verify it is a set updated after it was created + // TODO: This is currently failing. + assert.True(gotUpdateTime.After(hCreated), "Updated set should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, hCreated) + + // Clear all values which are hard to compare against. + got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil + } + if tc.res != nil { + tc.res.Item.Version = version + 1 + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "UpdateHost(%q) got response %q, wanted %q", req, got, tc.res) + }) + } +}