From b3fd23863f5e8d0c40202832f70de853f9d03158 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 13 Nov 2021 17:18:20 -0500 Subject: [PATCH] Add external ID to host output (#1705) This also fixes a bug if no hosts were returned where the job would run over and over in a loop, and some other cleanup. --- api/hosts/host.gen.go | 1 + globals/fields.go | 1 + internal/cmd/commands/hostscmd/funcs.go | 8 +++ internal/gen/controller.swagger.json | 5 ++ internal/host/plugin/job_set_sync.go | 6 +- internal/host/plugin/job_set_sync_test.go | 2 +- internal/host/plugin/loopback.go | 64 +++++++++++++++---- .../api/resources/hosts/v1/host.proto | 3 + .../controller/handlers/hosts/host_service.go | 36 +++++------ .../handlers/hosts/host_service_test.go | 5 +- .../controller/handlers/option_test.go | 12 ++++ .../servers/controller/handlers/options.go | 9 +++ .../controller/api/resources/hosts/host.pb.go | 49 ++++++++------ 13 files changed, 147 insertions(+), 54 deletions(-) diff --git a/api/hosts/host.gen.go b/api/hosts/host.gen.go index 718ffd667..b8c199ed7 100644 --- a/api/hosts/host.gen.go +++ b/api/hosts/host.gen.go @@ -28,6 +28,7 @@ type Host struct { Attributes map[string]interface{} `json:"attributes,omitempty"` IpAddresses []string `json:"ip_addresses,omitempty"` DnsNames []string `json:"dns_names,omitempty"` + ExternalId string `json:"external_id,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` response *api.Response diff --git a/globals/fields.go b/globals/fields.go index 684411135..20a7045b6 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -65,6 +65,7 @@ const ( PluginNameField = "plugin_name" IpAddressesField = "ip_addresses" DnsNamesField = "dns_names" + ExternalIdField = "external_id" EgressCredentialSourceIdsField = "egress_credential_source_ids" EgressCredentialSourcesField = "egress_credential_sources" ) diff --git a/internal/cmd/commands/hostscmd/funcs.go b/internal/cmd/commands/hostscmd/funcs.go index f7fdfb749..5a524a088 100644 --- a/internal/cmd/commands/hostscmd/funcs.go +++ b/internal/cmd/commands/hostscmd/funcs.go @@ -77,6 +77,11 @@ func (c *Command) printListTable(items []*hosts.Host) string { fmt.Sprintf(" ID: %s", "(not available)"), ) } + if item.ExternalId != "" { + output = append(output, + fmt.Sprintf(" External ID: %s", item.ExternalId), + ) + } if item.Version > 0 { output = append(output, fmt.Sprintf(" Version: %d", item.Version), @@ -135,6 +140,9 @@ func printItemTable(result api.GenericResult) string { if item.HostCatalogId != "" { nonAttributeMap["Host Catalog ID"] = item.HostCatalogId } + if item.ExternalId != "" { + nonAttributeMap["External ID"] = item.ExternalId + } maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 8723b2ace..8a0ce1bdc 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -4247,6 +4247,11 @@ "description": "Output only. The list of dns addresses associated with this host.", "readOnly": true }, + "external_id": { + "type": "string", + "description": "Output only. The external ID of the host, if any.", + "readOnly": true + }, "authorized_actions": { "type": "array", "items": { diff --git a/internal/host/plugin/job_set_sync.go b/internal/host/plugin/job_set_sync.go index 4f58ad1c6..e2727f395 100644 --- a/internal/host/plugin/job_set_sync.go +++ b/internal/host/plugin/job_set_sync.go @@ -299,8 +299,10 @@ func (r *SetSyncJob) syncSets(ctx context.Context, setIds []string) error { return errors.Wrap(ctx, err, op) } - if _, err := r.upsertHosts(ctx, ci.storeCat, catSetIds, resp.GetHosts()); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("upserting hosts")) + if len(resp.GetHosts()) > 0 { + if _, err := r.upsertHosts(ctx, ci.storeCat, catSetIds, resp.GetHosts()); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("upserting hosts")) + } } updateSyncDataQuery := ` diff --git a/internal/host/plugin/job_set_sync_test.go b/internal/host/plugin/job_set_sync_test.go index 96347f301..63c3c6c94 100644 --- a/internal/host/plugin/job_set_sync_test.go +++ b/internal/host/plugin/job_set_sync_test.go @@ -170,7 +170,7 @@ func TestSetSyncJob_Run(t *testing.T) { { ExternalId: "first", IpAddresses: []string{"10.0.0.1"}, - DnsNames: []string{"foo.com"}, + DnsNames: []string{"foo.com"}, SetIds: setIds, }, }, diff --git a/internal/host/plugin/loopback.go b/internal/host/plugin/loopback.go index 34f32a69a..f77b060ef 100644 --- a/internal/host/plugin/loopback.go +++ b/internal/host/plugin/loopback.go @@ -37,8 +37,9 @@ func NewLoopbackPlugin() plgpb.HostPluginServiceServer { hostMap: make(map[string][]*loopbackPluginHostInfo), } ret.OnCreateCatalogFn = ret.onCreateCatalog - ret.OnCreateSetFn = ret.onCreateSet ret.OnUpdateCatalogFn = ret.onUpdateCatalog + ret.OnCreateSetFn = ret.onCreateSet + ret.OnUpdateSetFn = ret.onUpdateSet ret.OnDeleteSetFn = ret.onDeleteSet ret.ListHostsFn = ret.listHosts return ret @@ -61,6 +62,23 @@ func (l *loopbackPlugin) onCreateCatalog(ctx context.Context, req *plgpb.OnCreat return &plgpb.OnCreateCatalogResponse{}, nil } +func (l *loopbackPlugin) onUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { + const op = "plugin.(loopbackPlugin).onUpdateCatalog" + if req == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") + } + if cat := req.GetNewCatalog(); cat != nil { + if secrets := cat.GetSecrets(); secrets != nil { + return &plgpb.OnUpdateCatalogResponse{ + Persisted: &plgpb.HostCatalogPersisted{ + Secrets: secrets, + }, + }, nil + } + } + return &plgpb.OnUpdateCatalogResponse{}, nil +} + func (l *loopbackPlugin) onCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { const op = "plugin.(loopbackPlugin).onCreateSet" if req == nil { @@ -93,24 +111,46 @@ func (l *loopbackPlugin) onCreateSet(ctx context.Context, req *plgpb.OnCreateSet } } } - return nil, nil + return &plgpb.OnCreateSetResponse{}, nil } -func (l *loopbackPlugin) onUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { - const op = "plugin.(loopbackPlugin).onUpdateCatalog" +func (l *loopbackPlugin) onUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) { + const op = "plugin.(loopbackPlugin).onCreateSet" if req == nil { return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil") } - if cat := req.GetNewCatalog(); cat != nil { - if secrets := cat.GetSecrets(); secrets != nil { - return &plgpb.OnUpdateCatalogResponse{ - Persisted: &plgpb.HostCatalogPersisted{ - Secrets: secrets, - }, - }, nil + set := req.GetNewSet() + if set == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil") + } + var hosts []*loopbackPluginHostInfo + if attrs := set.GetAttributes(); attrs != nil { + attrsMap := attrs.AsMap() + if field := attrsMap[loopbackPluginHostInfoAttrField]; field != nil { + switch t := field.(type) { + case []interface{}: + for _, h := range t { + hostInfo := new(loopbackPluginHostInfo) + if err := mapstructure.Decode(h, hostInfo); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hosts = append(hosts, hostInfo) + } + case map[string]interface{}: + hostInfo := new(loopbackPluginHostInfo) + if err := mapstructure.Decode(t, hostInfo); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + hosts = append(hosts, hostInfo) + default: + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown host info type %T", t)) + } } } - return &plgpb.OnUpdateCatalogResponse{}, nil + if hosts != nil { + l.hostMap[set.GetId()] = hosts + } + return &plgpb.OnUpdateSetResponse{}, nil } func (l *loopbackPlugin) onDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) { 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 48b37ee90..2ac2732a7 100644 --- a/internal/proto/local/controller/api/resources/hosts/v1/host.proto +++ b/internal/proto/local/controller/api/resources/hosts/v1/host.proto @@ -56,6 +56,9 @@ message Host { // Output only. The list of dns addresses associated with this host. repeated string dns_names = 130; + // Output only. The external ID of the host, if any. + string external_id = 140; + // Output only. The available actions on this resource for this user. repeated string authorized_actions = 300 [json_name="authorized_actions"]; } diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go index 70ea91358..69548ba85 100644 --- a/internal/servers/controller/handlers/hosts/host_service.go +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -131,12 +131,11 @@ func (s Service) ListHosts(ctx context.Context, req *pbs.ListHostsRequest) (*pbs outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } - var hostSetIds []string switch h := item.(type) { case *plugin.Host: - hostSetIds = h.SetIds + outputOpts = append(outputOpts, handlers.WithHostSetIds(h.SetIds)) } - item, err := toProto(ctx, item, hostSetIds, outputOpts...) + item, err := toProto(ctx, item, outputOpts...) if err != nil { return nil, err } @@ -182,12 +181,11 @@ func (s Service) GetHost(ctx context.Context, req *pbs.GetHostRequest) (*pbs.Get outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } - var hostSetIds []string switch h := h.(type) { case *plugin.Host: - hostSetIds = h.SetIds + outputOpts = append(outputOpts, handlers.WithHostSetIds(h.SetIds)) } - item, err := toProto(ctx, h, hostSetIds, outputOpts...) + item, err := toProto(ctx, h, outputOpts...) if err != nil { return nil, err } @@ -226,7 +224,7 @@ func (s Service) CreateHost(ctx context.Context, req *pbs.CreateHostRequest) (*p outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } - item, err := toProto(ctx, h, nil, outputOpts...) + item, err := toProto(ctx, h, outputOpts...) if err != nil { return nil, err } @@ -268,7 +266,7 @@ func (s Service) UpdateHost(ctx context.Context, req *pbs.UpdateHostRequest) (*p outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, h.GetPublicId(), idActions).Strings())) } - item, err := toProto(ctx, h, nil, outputOpts...) + item, err := toProto(ctx, h, outputOpts...) if err != nil { return nil, err } @@ -530,7 +528,7 @@ func toPluginInfo(plg *hostplugin.Plugin) *plugins.PluginInfo { } } -func toProto(ctx context.Context, in host.Host, hostSetIds []string, opt ...handlers.Option) (*pb.Host, error) { +func toProto(ctx context.Context, in host.Host, opt ...handlers.Option) (*pb.Host, error) { opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building host proto") @@ -565,12 +563,7 @@ func toProto(ctx context.Context, in host.Host, hostSetIds []string, opt ...hand out.UpdatedTime = in.GetUpdateTime().GetTimestamp() } if outputFields.Has(globals.VersionField) { - switch in.(type) { - case *static.Host: - out.Version = in.GetVersion() - case *plugin.Host: - out.Version = 1 - } + out.Version = in.GetVersion() } if outputFields.Has(globals.ScopeField) { out.Scope = opts.WithScope @@ -578,10 +571,8 @@ func toProto(ctx context.Context, in host.Host, hostSetIds []string, opt ...hand if outputFields.Has(globals.AuthorizedActionsField) { out.AuthorizedActions = opts.WithAuthorizedActions } - if outputFields.Has(globals.HostSetIdsField) { - for _, hs := range hostSetIds { - out.HostSetIds = append(out.HostSetIds, hs) - } + if outputFields.Has(globals.HostSetIdsField) && len(opts.WithHostSetIds) > 0 { + out.HostSetIds = opts.WithHostSetIds } if outputFields.Has(globals.AttributesField) { switch h := in.(type) { @@ -604,6 +595,13 @@ func toProto(ctx context.Context, in host.Host, hostSetIds []string, opt ...hand if outputFields.Has(globals.DnsNamesField) { out.DnsNames = h.DnsNames } + if outputFields.Has(globals.ExternalIdField) { + out.ExternalId = h.ExternalId + } + // FIXME? Seems we should be _using_ a version... + if outputFields.Has(globals.VersionField) { + out.Version = 1 + } } return &out, nil } diff --git a/internal/servers/controller/handlers/hosts/host_service_test.go b/internal/servers/controller/handlers/hosts/host_service_test.go index 93dbe0d2d..946651663 100644 --- a/internal/servers/controller/handlers/hosts/host_service_test.go +++ b/internal/servers/controller/handlers/hosts/host_service_test.go @@ -174,6 +174,7 @@ func TestGet_Plugin(t *testing.T) { Description: plg.GetDescription(), }, HostSetIds: []string{hs.GetPublicId()}, + ExternalId: "test", AuthorizedActions: testAuthorizedActions[plugin.Subtype], } @@ -359,7 +360,8 @@ func TestList_Plugin(t *testing.T) { var wantHs []*pb.Host for i := 0; i < 10; i++ { - h := plugin.TestHost(t, conn, hc.GetPublicId(), fmt.Sprintf("host %d", i)) + extId := fmt.Sprintf("host %d", i) + h := plugin.TestHost(t, conn, hc.GetPublicId(), extId) plugin.TestSetMembers(t, conn, hs.GetPublicId(), []*plugin.Host{h}) wantHs = append(wantHs, &pb.Host{ Id: h.GetPublicId(), @@ -374,6 +376,7 @@ func TestList_Plugin(t *testing.T) { UpdatedTime: h.GetUpdateTime().GetTimestamp(), HostSetIds: []string{hs.GetPublicId()}, Version: 1, + ExternalId: extId, Type: plugin.Subtype.String(), AuthorizedActions: testAuthorizedActions[plugin.Subtype], }) diff --git a/internal/servers/controller/handlers/option_test.go b/internal/servers/controller/handlers/option_test.go index 17701a6e4..e8a9bbb1c 100644 --- a/internal/servers/controller/handlers/option_test.go +++ b/internal/servers/controller/handlers/option_test.go @@ -74,4 +74,16 @@ func Test_GetOpts(t *testing.T) { opts = GetOpts(WithMemberIds(out)) require.Equal(out, opts.WithMemberIds) }) + t.Run("WithHostSetIds", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + opts := GetOpts() + assert.Nil(opts.WithHostSetIds) + + out := []string{"foobar"} + + opts = GetOpts(WithHostSetIds(out)) + require.Equal(out, opts.WithHostSetIds) + }) } diff --git a/internal/servers/controller/handlers/options.go b/internal/servers/controller/handlers/options.go index c41c735df..423a95bca 100644 --- a/internal/servers/controller/handlers/options.go +++ b/internal/servers/controller/handlers/options.go @@ -32,6 +32,7 @@ type options struct { WithAuthorizedCollectionActions map[string]*structpb.ListValue WithManagedGroupIds []string WithMemberIds []string + WithHostSetIds []string } func getDefaultOptions() options { @@ -110,3 +111,11 @@ func WithMemberIds(ids []string) Option { o.WithMemberIds = ids } } + +// WithHostSetIds provides an option when creating responses to include the +// given host set IDs if allowed +func WithHostSetIds(ids []string) Option { + return func(o *options) { + o.WithHostSetIds = ids + } +} diff --git a/sdk/pbs/controller/api/resources/hosts/host.pb.go b/sdk/pbs/controller/api/resources/hosts/host.pb.go index e0dec3b5c..a41517da3 100644 --- a/sdk/pbs/controller/api/resources/hosts/host.pb.go +++ b/sdk/pbs/controller/api/resources/hosts/host.pb.go @@ -61,6 +61,8 @@ type Host struct { IpAddresses []string `protobuf:"bytes,120,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` // Output only. The list of dns addresses associated with this host. DnsNames []string `protobuf:"bytes,130,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` + // Output only. The external ID of the host, if any. + ExternalId string `protobuf:"bytes,140,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` // Output only. The available actions on this resource for this user. AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` } @@ -195,6 +197,13 @@ func (x *Host) GetDnsNames() []string { return nil } +func (x *Host) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + func (x *Host) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -272,8 +281,8 @@ var file_controller_api_resources_hosts_v1_host_proto_rawDesc = []byte{ 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 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, 0xfd, - 0x05, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, + 0x06, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, @@ -317,23 +326,25 @@ var file_controller_api_resources_hosts_v1_host_proto_rawDesc = []byte{ 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x78, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x82, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2f, 0x0a, - 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 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, 0x4c, 0x5a, 0x4a, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 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, + 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x20, 0x0a, + 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x8c, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, + 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 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, 0x4c, 0x5a, 0x4a, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, + 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 (