From 03951ec82a23a5285c19c67a27a997d3a11f670c Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Mon, 23 Oct 2023 16:35:35 -0700 Subject: [PATCH 1/4] config: add max_page_size variable The max_page_size variable lets operators control the size of the pagination pages. This value configures both the maximum permitted page size and the default page size when none is explicitly provided. Operators can tweak this option up or down to increase or reduce the load on the database during pagination. --- internal/cmd/config/config.go | 34 +++++- internal/cmd/config/config_load_test.go | 10 ++ internal/cmd/config/config_test.go | 102 ++++++++++++++++++ .../content/docs/configuration/controller.mdx | 4 + 4 files changed, 149 insertions(+), 1 deletion(-) diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 3beec298be8..12199ea65d2 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -216,6 +216,14 @@ type Controller struct { // License is the license used by HCP builds License string `hcl:"license"` + + // MaxPageSize overrides the default and max page size. + // The default page size is what is used when the page size + // is not explicitly provided by the user. The max page size + // is the greatest number the page size can be set to before + // it is rejected by the controller. + MaxPageSizeRaw any `hcl:"max_page_size"` + MaxPageSize uint `hcl:"-"` } func (c *Controller) InitNameIfEmpty(ctx context.Context) error { @@ -625,6 +633,31 @@ func Parse(d string) (*Config, error) { return nil, errors.New("Controller liveness time to stale value is negative") } + if result.Controller.MaxPageSizeRaw != nil { + switch t := result.Controller.MaxPageSizeRaw.(type) { + case string: + maxPageSizeString, err := parseutil.ParsePath(t) + if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) { + return nil, fmt.Errorf("Error parsing max page size: %w", err) + } + pageSize, err := strconv.Atoi(maxPageSizeString) + if err != nil { + return nil, fmt.Errorf("Max page size value is not an int: %w", err) + } + if pageSize <= 0 { + return nil, fmt.Errorf("Max page size value must be at least 1, was %d", pageSize) + } + result.Controller.MaxPageSize = uint(pageSize) + case int: + if t <= 0 { + return nil, fmt.Errorf("Max page size value must be at least 1, was %d", t) + } + result.Controller.MaxPageSize = uint(t) + default: + return nil, fmt.Errorf("Max page size: unsupported type %q", reflect.TypeOf(t).String()) + } + } + if result.Controller.Database != nil { if result.Controller.Database.MaxOpenConnectionsRaw != nil { switch t := result.Controller.Database.MaxOpenConnectionsRaw.(type) { @@ -680,7 +713,6 @@ func Parse(d string) (*Config, error) { reflect.TypeOf(t).String()) } } - } } diff --git a/internal/cmd/config/config_load_test.go b/internal/cmd/config/config_load_test.go index 392174cc242..7fd6c1fb6a6 100644 --- a/internal/cmd/config/config_load_test.go +++ b/internal/cmd/config/config_load_test.go @@ -416,6 +416,8 @@ func TestLoad(t *testing.T) { GracefulShutdownWaitDuration: 0, WorkerStatusGracePeriodDuration: 0, LivenessTimeToStaleDuration: 0, + MaxPageSizeRaw: nil, + MaxPageSize: 0, }, DevController: false, DevUiPassthroughDir: "", @@ -839,6 +841,8 @@ func TestLoad(t *testing.T) { GracefulShutdownWaitDuration: 0, WorkerStatusGracePeriodDuration: 0, LivenessTimeToStaleDuration: 0, + MaxPageSizeRaw: nil, + MaxPageSize: 0, }, DevController: false, DevUiPassthroughDir: "", @@ -1257,6 +1261,8 @@ func TestLoad(t *testing.T) { GracefulShutdownWaitDuration: 0, WorkerStatusGracePeriodDuration: 0, LivenessTimeToStaleDuration: 0, + MaxPageSizeRaw: nil, + MaxPageSize: 0, }, DevController: false, DevUiPassthroughDir: "", @@ -1675,6 +1681,8 @@ func TestLoad(t *testing.T) { GracefulShutdownWaitDuration: 0, WorkerStatusGracePeriodDuration: 0, LivenessTimeToStaleDuration: 0, + MaxPageSizeRaw: nil, + MaxPageSize: 0, }, DevController: false, DevUiPassthroughDir: "", @@ -1764,6 +1772,8 @@ func TestLoad(t *testing.T) { GracefulShutdownWaitDuration: 0, WorkerStatusGracePeriodDuration: 0, LivenessTimeToStaleDuration: 0, + MaxPageSizeRaw: nil, + MaxPageSize: 0, }, DevController: false, DevUiPassthroughDir: "", diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 7aeef2af427..7de55294fb2 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -2474,3 +2474,105 @@ func TestSetupWorkerInitialUpstreams(t *testing.T) { }) } } + +func TestMaxPageSize(t *testing.T) { + tests := []struct { + name string + in string + envMaxPageSize string + expMaxPageSize uint + expErr bool + expErrStr string + }{ + { + name: "Valid integer value", + in: ` + controller { + name = "example-controller" + max_page_size = 5 + }`, + expMaxPageSize: 5, + expErr: false, + }, + { + name: "Valid string value", + in: ` + controller { + name = "example-controller" + max_page_size = "5" + }`, + expMaxPageSize: 5, + expErr: false, + }, + { + name: "Invalid value integer", + in: ` + controller { + name = "example-controller" + max_page_size = 0 + }`, + expErr: true, + expErrStr: "Max page size value must be at least 1, was 0", + }, + { + name: "Invalid value string", + in: ` + controller { + name = "example-controller" + max_page_size = "string bad" + }`, + expErr: true, + expErrStr: "Max page size value is not an int: " + + "strconv.Atoi: parsing \"string bad\": invalid syntax", + }, + { + name: "Invalid value type", + in: ` + controller { + name = "example-controller" + max_page_size = false + }`, + expErr: true, + expErrStr: "Max page size: unsupported type \"bool\"", + }, + { + name: "Valid env var", + in: ` + controller { + name = "example-controller" + max_page_size = "env://ENV_MAX_CONN" + }`, + expMaxPageSize: 8, + envMaxPageSize: "8", + expErr: false, + }, + { + name: "Invalid env var", + in: ` + controller { + name = "example-controller" + max_page_size = "env://ENV_MAX_CONN" + }`, + envMaxPageSize: "bogus value", + expErr: true, + expErrStr: "Max page size value is not an int: " + + "strconv.Atoi: parsing \"bogus value\": invalid syntax", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("ENV_MAX_CONN", tt.envMaxPageSize) + c, err := Parse(tt.in) + if tt.expErr { + require.EqualError(t, err, tt.expErrStr) + require.Nil(t, c) + return + } + + require.NoError(t, err) + require.NotNil(t, c) + require.NotNil(t, c.Controller) + require.Equal(t, tt.expMaxPageSize, c.Controller.MaxPageSize) + }) + } +} diff --git a/website/content/docs/configuration/controller.mdx b/website/content/docs/configuration/controller.mdx index 36f4d312f5e..b4e95507fa6 100644 --- a/website/content/docs/configuration/controller.mdx +++ b/website/content/docs/configuration/controller.mdx @@ -108,6 +108,10 @@ description will be read. are anything specified by Go's [ParseDuration()](https://golang.org/pkg/time/#ParseDuration) method. Only used when an `ops` listener is set and the Controller is present. Default is 0 seconds. +- `max_page_size` - The max allowed page size when paginating. If a user specifies a page size greater than + this number, it will be truncated to this number. This is also used as the default page size for any request + that don't explicitly specify a page size. Default is 1000. + ## Signals The `SIGHUP` signal causes a controller to reload its configuration file to pick up any updates to the `database url` value. Any other updated values are ignored. From 69bc8ffa1c823759ea7df7832a1cf92ec43d2855 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Mon, 23 Oct 2023 16:49:32 -0700 Subject: [PATCH 2/4] api: add automatic list pagination Updates all List endpoints to automatically paginate through results to preserve backwards compatibility. Adding support for user controllable pagination could be considered at a later time. --- api/accounts/account.gen.go | 97 +++++++++++++++- api/accounts/option.gen.go | 12 ++ api/authmethods/authmethods.gen.go | 97 +++++++++++++++- api/authmethods/option.gen.go | 12 ++ api/authtokens/authtokens.gen.go | 97 +++++++++++++++- api/authtokens/option.gen.go | 12 ++ .../credential_library.gen.go | 97 +++++++++++++++- api/credentiallibraries/option.gen.go | 12 ++ api/credentials/credential.gen.go | 97 +++++++++++++++- api/credentials/option.gen.go | 12 ++ api/credentialstores/credential_store.gen.go | 97 +++++++++++++++- api/credentialstores/option.gen.go | 12 ++ api/go.mod | 1 + api/go.sum | 3 +- api/groups/group.gen.go | 97 +++++++++++++++- api/groups/option.gen.go | 12 ++ api/hostcatalogs/host_catalog.gen.go | 97 +++++++++++++++- api/hostcatalogs/option.gen.go | 12 ++ api/hosts/host.gen.go | 97 +++++++++++++++- api/hosts/option.gen.go | 12 ++ api/hostsets/host_set.gen.go | 97 +++++++++++++++- api/hostsets/option.gen.go | 12 ++ api/managedgroups/managedgroups.gen.go | 97 +++++++++++++++- api/managedgroups/option.gen.go | 12 ++ api/roles/option.gen.go | 12 ++ api/roles/role.gen.go | 97 +++++++++++++++- api/scopes/option.gen.go | 12 ++ api/scopes/scope.gen.go | 97 +++++++++++++++- api/sessionrecordings/option.gen.go | 12 ++ .../session_recording.gen.go | 97 +++++++++++++++- api/sessions/option.gen.go | 12 ++ api/sessions/session.gen.go | 97 +++++++++++++++- api/storagebuckets/option.gen.go | 12 ++ api/storagebuckets/storage_bucket.gen.go | 97 +++++++++++++++- api/targets/option.gen.go | 12 ++ api/targets/target.gen.go | 97 +++++++++++++++- api/users/option.gen.go | 12 ++ api/users/user.gen.go | 97 +++++++++++++++- api/workers/option.gen.go | 12 ++ api/workers/worker.gen.go | 97 +++++++++++++++- internal/api/genapi/templates.go | 109 +++++++++++++++++- 41 files changed, 2142 insertions(+), 42 deletions(-) diff --git a/api/accounts/account.gen.go b/api/accounts/account.gen.go index 60c694438d9..8ec3e773495 100644 --- a/api/accounts/account.gen.go +++ b/api/accounts/account.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -62,14 +63,34 @@ func (n AccountDeleteResult) GetResponse() *api.Response { } type AccountListResult struct { - Items []*Account - response *api.Response + Items []*Account `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n AccountListResult) GetItems() []*Account { return n.Items } +func (n AccountListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n AccountListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n AccountListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n AccountListResult) GetResponseType() string { + return n.ResponseType +} + func (n AccountListResult) GetResponse() *api.Response { return n.response } @@ -320,5 +341,77 @@ func (c *Client) List(ctx context.Context, authMethodId string, opt ...Option) ( return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "accounts", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(AccountListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Account) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/accounts/option.gen.go b/api/accounts/option.gen.go index 7d6ef19552e..c2c69dfd1eb 100644 --- a/api/accounts/option.gen.go +++ b/api/accounts/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/authmethods/authmethods.gen.go b/api/authmethods/authmethods.gen.go index 51369be2966..4f303ddb585 100644 --- a/api/authmethods/authmethods.gen.go +++ b/api/authmethods/authmethods.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -63,14 +64,34 @@ func (n AuthMethodDeleteResult) GetResponse() *api.Response { } type AuthMethodListResult struct { - Items []*AuthMethod - response *api.Response + Items []*AuthMethod `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n AuthMethodListResult) GetItems() []*AuthMethod { return n.Items } +func (n AuthMethodListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n AuthMethodListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n AuthMethodListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n AuthMethodListResult) GetResponseType() string { + return n.ResponseType +} + func (n AuthMethodListResult) GetResponse() *api.Response { return n.response } @@ -326,5 +347,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Auth return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "auth-methods", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(AuthMethodListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *AuthMethod) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/authmethods/option.gen.go b/api/authmethods/option.gen.go index dcba776e600..ebd29afc156 100644 --- a/api/authmethods/option.gen.go +++ b/api/authmethods/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/authtokens/authtokens.gen.go b/api/authtokens/authtokens.gen.go index d0fee9dbdf1..0a458cbd40c 100644 --- a/api/authtokens/authtokens.gen.go +++ b/api/authtokens/authtokens.gen.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -60,14 +61,34 @@ func (n AuthTokenDeleteResult) GetResponse() *api.Response { } type AuthTokenListResult struct { - Items []*AuthToken - response *api.Response + Items []*AuthToken `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n AuthTokenListResult) GetItems() []*AuthToken { return n.Items } +func (n AuthTokenListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n AuthTokenListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n AuthTokenListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n AuthTokenListResult) GetResponseType() string { + return n.ResponseType +} + func (n AuthTokenListResult) GetResponse() *api.Response { return n.response } @@ -211,5 +232,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Auth return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "auth-tokens", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(AuthTokenListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *AuthToken) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/authtokens/option.gen.go b/api/authtokens/option.gen.go index de4d894d4e7..24492f20d22 100644 --- a/api/authtokens/option.gen.go +++ b/api/authtokens/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -64,6 +68,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/credentiallibraries/credential_library.gen.go b/api/credentiallibraries/credential_library.gen.go index 6a43b5e154f..e4a352684d5 100644 --- a/api/credentiallibraries/credential_library.gen.go +++ b/api/credentiallibraries/credential_library.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -63,14 +64,34 @@ func (n CredentialLibraryDeleteResult) GetResponse() *api.Response { } type CredentialLibraryListResult struct { - Items []*CredentialLibrary - response *api.Response + Items []*CredentialLibrary `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n CredentialLibraryListResult) GetItems() []*CredentialLibrary { return n.Items } +func (n CredentialLibraryListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n CredentialLibraryListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n CredentialLibraryListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n CredentialLibraryListResult) GetResponseType() string { + return n.ResponseType +} + func (n CredentialLibraryListResult) GetResponse() *api.Response { return n.response } @@ -326,5 +347,77 @@ func (c *Client) List(ctx context.Context, credentialStoreId string, opt ...Opti return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "credential-libraries", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(CredentialLibraryListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *CredentialLibrary) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/credentiallibraries/option.gen.go b/api/credentiallibraries/option.gen.go index 37deb83dd88..8088153c907 100644 --- a/api/credentiallibraries/option.gen.go +++ b/api/credentiallibraries/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/credentials/credential.gen.go b/api/credentials/credential.gen.go index c0df55609ba..bf82cccd6d0 100644 --- a/api/credentials/credential.gen.go +++ b/api/credentials/credential.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -61,14 +62,34 @@ func (n CredentialDeleteResult) GetResponse() *api.Response { } type CredentialListResult struct { - Items []*Credential - response *api.Response + Items []*Credential `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n CredentialListResult) GetItems() []*Credential { return n.Items } +func (n CredentialListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n CredentialListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n CredentialListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n CredentialListResult) GetResponseType() string { + return n.ResponseType +} + func (n CredentialListResult) GetResponse() *api.Response { return n.response } @@ -324,5 +345,77 @@ func (c *Client) List(ctx context.Context, credentialStoreId string, opt ...Opti return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "credentials", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(CredentialListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Credential) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/credentials/option.gen.go b/api/credentials/option.gen.go index 201ae595cd1..b4035741447 100644 --- a/api/credentials/option.gen.go +++ b/api/credentials/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/credentialstores/credential_store.gen.go b/api/credentialstores/credential_store.gen.go index a0dbb8a924d..b7b241627b1 100644 --- a/api/credentialstores/credential_store.gen.go +++ b/api/credentialstores/credential_store.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -62,14 +63,34 @@ func (n CredentialStoreDeleteResult) GetResponse() *api.Response { } type CredentialStoreListResult struct { - Items []*CredentialStore - response *api.Response + Items []*CredentialStore `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n CredentialStoreListResult) GetItems() []*CredentialStore { return n.Items } +func (n CredentialStoreListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n CredentialStoreListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n CredentialStoreListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n CredentialStoreListResult) GetResponseType() string { + return n.ResponseType +} + func (n CredentialStoreListResult) GetResponse() *api.Response { return n.response } @@ -325,5 +346,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Cred return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "credential-stores", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(CredentialStoreListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *CredentialStore) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/credentialstores/option.gen.go b/api/credentialstores/option.gen.go index 4b590b6d9c3..13e8443187c 100644 --- a/api/credentialstores/option.gen.go +++ b/api/credentialstores/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/go.mod b/api/go.mod index 580a747e514..e79ed08aea0 100644 --- a/api/go.mod +++ b/api/go.mod @@ -33,6 +33,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/tools v0.2.0 // indirect google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/api/go.sum b/api/go.sum index 93c5ae7c554..35aa8ee5dac 100644 --- a/api/go.sum +++ b/api/go.sum @@ -152,7 +152,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/api/groups/group.gen.go b/api/groups/group.gen.go index 80a6e950a52..be799ee68e5 100644 --- a/api/groups/group.gen.go +++ b/api/groups/group.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -61,14 +62,34 @@ func (n GroupDeleteResult) GetResponse() *api.Response { } type GroupListResult struct { - Items []*Group - response *api.Response + Items []*Group `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n GroupListResult) GetItems() []*Group { return n.Items } +func (n GroupListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n GroupListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n GroupListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n GroupListResult) GetResponseType() string { + return n.ResponseType +} + func (n GroupListResult) GetResponse() *api.Response { return n.response } @@ -319,6 +340,78 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Grou return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "groups", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(GroupListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Group) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/groups/option.gen.go b/api/groups/option.gen.go index c6259f86ad5..70e93d0b284 100644 --- a/api/groups/option.gen.go +++ b/api/groups/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/hostcatalogs/host_catalog.gen.go b/api/hostcatalogs/host_catalog.gen.go index 7776ec6913f..7625539c2d5 100644 --- a/api/hostcatalogs/host_catalog.gen.go +++ b/api/hostcatalogs/host_catalog.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -67,14 +68,34 @@ func (n HostCatalogDeleteResult) GetResponse() *api.Response { } type HostCatalogListResult struct { - Items []*HostCatalog - response *api.Response + Items []*HostCatalog `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n HostCatalogListResult) GetItems() []*HostCatalog { return n.Items } +func (n HostCatalogListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n HostCatalogListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n HostCatalogListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n HostCatalogListResult) GetResponseType() string { + return n.ResponseType +} + func (n HostCatalogListResult) GetResponse() *api.Response { return n.response } @@ -330,5 +351,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Host return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "host-catalogs", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(HostCatalogListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *HostCatalog) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/hostcatalogs/option.gen.go b/api/hostcatalogs/option.gen.go index 5ef7ca49e4b..fe80ca344a0 100644 --- a/api/hostcatalogs/option.gen.go +++ b/api/hostcatalogs/option.gen.go @@ -27,6 +27,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -51,6 +52,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -75,6 +79,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/hosts/host.gen.go b/api/hosts/host.gen.go index 3758db64c3a..11eeea429db 100644 --- a/api/hosts/host.gen.go +++ b/api/hosts/host.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -68,14 +69,34 @@ func (n HostDeleteResult) GetResponse() *api.Response { } type HostListResult struct { - Items []*Host - response *api.Response + Items []*Host `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n HostListResult) GetItems() []*Host { return n.Items } +func (n HostListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n HostListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n HostListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n HostListResult) GetResponseType() string { + return n.ResponseType +} + func (n HostListResult) GetResponse() *api.Response { return n.response } @@ -326,5 +347,77 @@ func (c *Client) List(ctx context.Context, hostCatalogId string, opt ...Option) return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "hosts", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(HostListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Host) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/hosts/option.gen.go b/api/hosts/option.gen.go index 6e8da1836f2..532b5884d61 100644 --- a/api/hosts/option.gen.go +++ b/api/hosts/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/hostsets/host_set.gen.go b/api/hostsets/host_set.gen.go index e4ca5850bc6..19528c9fce0 100644 --- a/api/hostsets/host_set.gen.go +++ b/api/hostsets/host_set.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -66,14 +67,34 @@ func (n HostSetDeleteResult) GetResponse() *api.Response { } type HostSetListResult struct { - Items []*HostSet - response *api.Response + Items []*HostSet `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n HostSetListResult) GetItems() []*HostSet { return n.Items } +func (n HostSetListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n HostSetListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n HostSetListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n HostSetListResult) GetResponseType() string { + return n.ResponseType +} + func (n HostSetListResult) GetResponse() *api.Response { return n.response } @@ -324,6 +345,78 @@ func (c *Client) List(ctx context.Context, hostCatalogId string, opt ...Option) return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "host-sets", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(HostSetListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *HostSet) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/hostsets/option.gen.go b/api/hostsets/option.gen.go index e17f8c745ab..7d2fa8f8bff 100644 --- a/api/hostsets/option.gen.go +++ b/api/hostsets/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/managedgroups/managedgroups.gen.go b/api/managedgroups/managedgroups.gen.go index be4fb3d7279..5588c29188e 100644 --- a/api/managedgroups/managedgroups.gen.go +++ b/api/managedgroups/managedgroups.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -62,14 +63,34 @@ func (n ManagedGroupDeleteResult) GetResponse() *api.Response { } type ManagedGroupListResult struct { - Items []*ManagedGroup - response *api.Response + Items []*ManagedGroup `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n ManagedGroupListResult) GetItems() []*ManagedGroup { return n.Items } +func (n ManagedGroupListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n ManagedGroupListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n ManagedGroupListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n ManagedGroupListResult) GetResponseType() string { + return n.ResponseType +} + func (n ManagedGroupListResult) GetResponse() *api.Response { return n.response } @@ -320,5 +341,77 @@ func (c *Client) List(ctx context.Context, authMethodId string, opt ...Option) ( return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "managed-groups", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(ManagedGroupListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *ManagedGroup) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/managedgroups/option.gen.go b/api/managedgroups/option.gen.go index 82e2c7ad7c5..87c9cdaea91 100644 --- a/api/managedgroups/option.gen.go +++ b/api/managedgroups/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string } func getDefaultOptions() options { @@ -48,6 +49,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } return opts, apiOpts } @@ -69,6 +73,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/roles/option.gen.go b/api/roles/option.gen.go index a2ae657848f..b0010a48326 100644 --- a/api/roles/option.gen.go +++ b/api/roles/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/roles/role.gen.go b/api/roles/role.gen.go index 33e189f94b7..ae497a7d5c0 100644 --- a/api/roles/role.gen.go +++ b/api/roles/role.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -64,14 +65,34 @@ func (n RoleDeleteResult) GetResponse() *api.Response { } type RoleListResult struct { - Items []*Role - response *api.Response + Items []*Role `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n RoleListResult) GetItems() []*Role { return n.Items } +func (n RoleListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n RoleListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n RoleListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n RoleListResult) GetResponseType() string { + return n.ResponseType +} + func (n RoleListResult) GetResponse() *api.Response { return n.response } @@ -322,6 +343,78 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Role return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "roles", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(RoleListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Role) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/scopes/option.gen.go b/api/scopes/option.gen.go index a28f2493d0d..10ae82e7f14 100644 --- a/api/scopes/option.gen.go +++ b/api/scopes/option.gen.go @@ -27,6 +27,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -51,6 +52,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -75,6 +79,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/scopes/scope.gen.go b/api/scopes/scope.gen.go index b71068fdfa1..ac3da24a9f6 100644 --- a/api/scopes/scope.gen.go +++ b/api/scopes/scope.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -61,14 +62,34 @@ func (n ScopeDeleteResult) GetResponse() *api.Response { } type ScopeListResult struct { - Items []*Scope - response *api.Response + Items []*Scope `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n ScopeListResult) GetItems() []*Scope { return n.Items } +func (n ScopeListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n ScopeListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n ScopeListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n ScopeListResult) GetResponseType() string { + return n.ResponseType +} + func (n ScopeListResult) GetResponse() *api.Response { return n.response } @@ -319,5 +340,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Scop return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "scopes", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(ScopeListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Scope) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/sessionrecordings/option.gen.go b/api/sessionrecordings/option.gen.go index 34ea486e985..aaf4608048d 100644 --- a/api/sessionrecordings/option.gen.go +++ b/api/sessionrecordings/option.gen.go @@ -25,6 +25,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -49,6 +50,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -63,6 +67,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithRecursive tells the API to use recursion for listing operations on this // resource func WithRecursive(recurse bool) Option { diff --git a/api/sessionrecordings/session_recording.gen.go b/api/sessionrecordings/session_recording.gen.go index 90c908c2297..1ea1f8028fa 100644 --- a/api/sessionrecordings/session_recording.gen.go +++ b/api/sessionrecordings/session_recording.gen.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -52,14 +53,34 @@ func (n SessionRecordingReadResult) GetResponse() *api.Response { } type SessionRecordingListResult struct { - Items []*SessionRecording - response *api.Response + Items []*SessionRecording `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n SessionRecordingListResult) GetItems() []*SessionRecording { return n.Items } +func (n SessionRecordingListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n SessionRecordingListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n SessionRecordingListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n SessionRecordingListResult) GetResponseType() string { + return n.ResponseType +} + func (n SessionRecordingListResult) GetResponse() *api.Response { return n.response } @@ -161,5 +182,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Sess return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "session-recordings", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(SessionRecordingListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *SessionRecording) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/sessions/option.gen.go b/api/sessions/option.gen.go index 4f91a4c518e..517869fb9ce 100644 --- a/api/sessions/option.gen.go +++ b/api/sessions/option.gen.go @@ -27,6 +27,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -51,6 +52,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -75,6 +79,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/sessions/session.gen.go b/api/sessions/session.gen.go index 03916ad06ec..b19149f1282 100644 --- a/api/sessions/session.gen.go +++ b/api/sessions/session.gen.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -69,14 +70,34 @@ func (n SessionDeleteResult) GetResponse() *api.Response { } type SessionListResult struct { - Items []*Session - response *api.Response + Items []*Session `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n SessionListResult) GetItems() []*Session { return n.Items } +func (n SessionListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n SessionListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n SessionListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n SessionListResult) GetResponseType() string { + return n.ResponseType +} + func (n SessionListResult) GetResponse() *api.Response { return n.response } @@ -178,5 +199,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Sess return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "sessions", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(SessionListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Session) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/storagebuckets/option.gen.go b/api/storagebuckets/option.gen.go index 1a388de5d42..7c28e932241 100644 --- a/api/storagebuckets/option.gen.go +++ b/api/storagebuckets/option.gen.go @@ -27,6 +27,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -51,6 +52,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -75,6 +79,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/storagebuckets/storage_bucket.gen.go b/api/storagebuckets/storage_bucket.gen.go index 1558604cfae..b1563d92a55 100644 --- a/api/storagebuckets/storage_bucket.gen.go +++ b/api/storagebuckets/storage_bucket.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -69,14 +70,34 @@ func (n StorageBucketDeleteResult) GetResponse() *api.Response { } type StorageBucketListResult struct { - Items []*StorageBucket - response *api.Response + Items []*StorageBucket `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n StorageBucketListResult) GetItems() []*StorageBucket { return n.Items } +func (n StorageBucketListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n StorageBucketListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n StorageBucketListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n StorageBucketListResult) GetResponseType() string { + return n.ResponseType +} + func (n StorageBucketListResult) GetResponse() *api.Response { return n.response } @@ -327,5 +348,77 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Stor return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "storage-buckets", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(StorageBucketListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *StorageBucket) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/targets/option.gen.go b/api/targets/option.gen.go index ba4ac8ce929..b34abbb4915 100644 --- a/api/targets/option.gen.go +++ b/api/targets/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/targets/target.gen.go b/api/targets/target.gen.go index 473dc9231cb..5b1b3a2d26c 100644 --- a/api/targets/target.gen.go +++ b/api/targets/target.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -73,14 +74,34 @@ func (n TargetDeleteResult) GetResponse() *api.Response { } type TargetListResult struct { - Items []*Target - response *api.Response + Items []*Target `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n TargetListResult) GetItems() []*Target { return n.Items } +func (n TargetListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n TargetListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n TargetListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n TargetListResult) GetResponseType() string { + return n.ResponseType +} + func (n TargetListResult) GetResponse() *api.Response { return n.response } @@ -336,6 +357,78 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Targ return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "targets", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(TargetListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Target) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/users/option.gen.go b/api/users/option.gen.go index 0762d1b35bc..18092d471f8 100644 --- a/api/users/option.gen.go +++ b/api/users/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/users/user.gen.go b/api/users/user.gen.go index 6df55ba96cc..df14657931f 100644 --- a/api/users/user.gen.go +++ b/api/users/user.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -65,14 +66,34 @@ func (n UserDeleteResult) GetResponse() *api.Response { } type UserListResult struct { - Items []*User - response *api.Response + Items []*User `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n UserListResult) GetItems() []*User { return n.Items } +func (n UserListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n UserListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n UserListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n UserListResult) GetResponseType() string { + return n.ResponseType +} + func (n UserListResult) GetResponse() *api.Response { return n.response } @@ -323,6 +344,78 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*User return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "users", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(UserListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *User) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/api/workers/option.gen.go b/api/workers/option.gen.go index bdaa5722470..3bdc8f732ed 100644 --- a/api/workers/option.gen.go +++ b/api/workers/option.gen.go @@ -26,6 +26,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string withRecursive bool } @@ -50,6 +51,9 @@ func getOpts(opt ...Option) (options, []api.Option) { if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken + } if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) } @@ -74,6 +78,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by // hashicorp/go-bexpr. diff --git a/api/workers/worker.gen.go b/api/workers/worker.gen.go index 8d6743e771e..7b9bd381964 100644 --- a/api/workers/worker.gen.go +++ b/api/workers/worker.gen.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/url" + "slices" "time" "github.com/hashicorp/boundary/api" @@ -70,14 +71,34 @@ func (n WorkerDeleteResult) GetResponse() *api.Response { } type WorkerListResult struct { - Items []*Worker - response *api.Response + Items []*Worker `json:"items,omitempty"` + EstItemCount uint `json:"est_item_count,omitempty"` + RemovedIds []string `json:"removed_ids,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + ResponseType string `json:"response_type,omitempty"` + response *api.Response } func (n WorkerListResult) GetItems() []*Worker { return n.Items } +func (n WorkerListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n WorkerListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n WorkerListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n WorkerListResult) GetResponseType() string { + return n.ResponseType +} + func (n WorkerListResult) GetResponse() *api.Response { return n.response } @@ -377,6 +398,78 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Work return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "workers", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new(WorkerListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items) - 1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *Worker) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index df02ca72957..16bf6ff4f31 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -266,6 +266,78 @@ func (c *Client) List(ctx context.Context, {{ .CollectionFunctionArg }} string, return nil, apiErr } target.response = resp + if target.ResponseType == "complete" || target.ResponseType == "" { + return target, nil + } + // If there are more results, automatically fetch the rest of the results. + // idToIndex keeps a map from the ID of an item to its index in target.Items. + // This is used to update updated items in-place and remove deleted items + // from the result after pagination is done. + idToIndex := map[string]int{} + for i, item := range target.Items { + idToIndex[item.Id] = i + } + for { + req, err := c.client.NewRequest(ctx, "GET", "{{ .CollectionPath }}", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List request: %w", err) + } + + opts.queryMap["refresh_token"] = target.RefreshToken + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during List call: %w", err) + } + + page := new({{ .Name }}ListResult) + apiErr, err := resp.Decode(page) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + for _, item := range page.Items { + if i, ok := idToIndex[item.Id]; ok { + // Item has already been seen at index i, update in-place + target.Items[i] = item + } else { + target.Items = append(target.Items, item) + idToIndex[item.Id] = len(target.Items)-1 + } + } + target.RemovedIds = append(target.RemovedIds, page.RemovedIds...) + target.EstItemCount = page.EstItemCount + target.RefreshToken = page.RefreshToken + target.ResponseType = page.ResponseType + target.response = resp + if target.ResponseType == "complete" { + break + } + } + for _, removedId := range target.RemovedIds { + if i, ok := idToIndex[removedId]; ok { + // Remove the item at index i without preserving order + // https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order + target.Items[i] = target.Items[len(target.Items)-1] + target.Items = target.Items[:len(target.Items)-1] + // Update the index of the last element + idToIndex[target.Items[i].Id] = i + } + } + // Finally, sort the results again since in-place updates and deletes + // may have shuffled items. + slices.SortFunc(target.Items, func(i, j *{{ .Name }}) int { + return i.UpdatedTime.Compare(j.UpdatedTime) + }) return target, nil } `)) @@ -602,10 +674,9 @@ package {{ .Package }} import ( "context" "fmt" + "slices" "time" - "github.com/kr/pretty" - "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/scopes" ) @@ -652,7 +723,11 @@ func (n {{ .Name }}DeleteResult) GetResponse() *api.Response { {{ end }} {{ if ( hasResponseType .CreateResponseTypes "list" ) }} type {{ .Name }}ListResult struct { - Items []*{{ .Name }} + Items []*{{ .Name }} `, "`json:\"items,omitempty\"`", ` + EstItemCount uint `, "`json:\"est_item_count,omitempty\"`", ` + RemovedIds []string `, "`json:\"removed_ids,omitempty\"`", ` + RefreshToken string `, "`json:\"refresh_token,omitempty\"`", ` + ResponseType string `, "`json:\"response_type,omitempty\"`", ` response *api.Response } @@ -660,6 +735,22 @@ func (n {{ .Name }}ListResult) GetItems() []*{{ .Name }} { return n.Items } +func (n {{ .Name }}ListResult) GetEstItemCount() uint { + return n.EstItemCount +} + +func (n {{ .Name }}ListResult) GetRemovedIds() []string { + return n.RemovedIds +} + +func (n {{ .Name }}ListResult) GetRefreshToken() string { + return n.RefreshToken +} + +func (n {{ .Name }}ListResult) GetResponseType() string { + return n.ResponseType +} + func (n {{ .Name }}ListResult) GetResponse() *api.Response { return n.response } @@ -718,6 +809,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string {{ if .RecursiveListing }} withRecursive bool {{ end }} } @@ -741,6 +833,9 @@ func getOpts(opt ...Option) (options, []api.Option) { } if opts.withFilter != "" { opts.queryMap["filter"] = opts.withFilter + } + if opts.withRefreshToken != "" { + opts.queryMap["refresh_token"] = opts.withRefreshToken }{{ if .RecursiveListing }} if opts.withRecursive { opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) @@ -768,6 +863,14 @@ func WithSkipCurlOutput(skip bool) Option { } } +// WithRefreshToken tells the API to use the provided refresh token +// for listing operations on this resource. +func WithRefreshToken(refreshToken string) Option { + return func(o *options) { + o.withRefreshToken = refreshToken + } +} + {{ if not .SkipListFiltering }} // WithFilter tells the API to filter the items returned using the provided // filter term. The filter should be in a format supported by From d6f9f11acbff7385cf25e9096a7477caba2aa86a Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Tue, 24 Oct 2023 09:41:11 -0700 Subject: [PATCH 3/4] handlers/targets: add pagination support --- globals/globals.go | 3 + internal/daemon/controller/handler.go | 1 + .../handlers/targets/target_service.go | 150 +++- .../targets/tcp/target_service_test.go | 259 +++++- internal/gen/controller.swagger.json | 43 + .../api/services/target_service.pb.go | 755 ++++++++++-------- .../api/services/v1/target_service.proto | 29 + 7 files changed, 873 insertions(+), 367 deletions(-) diff --git a/globals/globals.go b/globals/globals.go index e0abb6b27d8..32c596820be 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -26,6 +26,9 @@ var ( // DefaultMaxRequestSize is the maximum size of a request we allow by default DefaultMaxRequestSize = int64(1024 * 1024) + // DefaultMaxPageSize is the maximum list page size allowed if not set in the config. + DefaultMaxPageSize = 1000 + // ContextMaxRequestSizeTypeKey is a value to keep linters from complaining // about clashing string identifiers ContextMaxRequestSizeTypeKey ContextMaxRequestSizeType diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index b461c3cb3dc..3060dc93397 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -228,6 +228,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { c.StaticCredentialRepoFn, c.downstreamWorkers, c.workerStatusGracePeriod, + c.conf.RawConfig.Controller.MaxPageSize, c.ControllerExtension, ) if err != nil { diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go index fb1679f8d45..158312c2ac7 100644 --- a/internal/daemon/controller/handlers/targets/target_service.go +++ b/internal/daemon/controller/handlers/targets/target_service.go @@ -29,7 +29,9 @@ import ( "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/pagination" "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/refreshtoken" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/session" @@ -121,6 +123,7 @@ type Service struct { downstreams common.Downstreamers kmsCache *kms.Kms workerStatusGracePeriod *atomic.Int64 + maxPageSize uint controllerExt intglobals.ControllerExtension } @@ -140,6 +143,7 @@ func NewService( staticCredRepoFn common.StaticCredentialRepoFactory, downstreams common.Downstreamers, workerStatusGracePeriod *atomic.Int64, + maxPageSize uint, controllerExt intglobals.ControllerExtension, ) (Service, error) { const op = "targets.NewService" @@ -170,6 +174,9 @@ func NewService( if staticCredRepoFn == nil { return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static credential repository") } + if maxPageSize == 0 { + maxPageSize = uint(globals.DefaultMaxPageSize) + } return Service{ repoFn: repoFn, iamRepoFn: iamRepoFn, @@ -182,13 +189,14 @@ func NewService( downstreams: downstreams, kmsCache: kmsCache, workerStatusGracePeriod: workerStatusGracePeriod, + maxPageSize: maxPageSize, controllerExt: controllerExt, }, nil } // ListTargets implements the interface pbs.TargetServiceServer. func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (*pbs.ListTargetsResponse, error) { - const op = "targets.(Service).ListSessions" + const op = "targets.(Service).ListTargets" if err := validateListRequest(ctx, req); err != nil { return nil, err @@ -224,50 +232,115 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) ( return &pbs.ListTargetsResponse{}, nil } - tl, err := s.listFromRepo(ctx, userPerms) + pageSize := int(s.maxPageSize) + // Use the requested page size only if it is smaller than + // the configured max. + if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize { + pageSize = int(req.GetPageSize()) + } + filter, err := handlers.NewFilter(ctx, req.GetFilter()) if err != nil { return nil, err } - if len(tl) == 0 { - return &pbs.ListTargetsResponse{}, nil - } - filter, err := handlers.NewFilter(ctx, req.GetFilter()) + repo, err := s.repoFn(target.WithPermissions(userPerms)) if err != nil { return nil, err } + // TODO: replace the need for this function with some way to convert the `filter` + // to a domain type. This would allow filtering to happen in the domain, and we could + // remove this callback altogether. + filterItemFn := func(ctx context.Context, item target.Target) (bool, error) { + pbItem, err := toProto(ctx, item, newOutputOpts(ctx, item, authResults, authzScopes)...) + if err != nil { + return false, err + } - finalItems := make([]*pb.Target, 0, len(tl)) - for _, item := range tl { - pr := perms.Resource{Id: item.GetPublicId(), ScopeId: item.GetProjectId(), Type: resource.Target} - outputFields := authResults.FetchOutputFields(pr, action.List).SelfOrDefaults(authResults.UserId) - - outputOpts := make([]handlers.Option, 0, 3) - outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + filterable, err := subtypes.Filterable(pbItem) + if err != nil { + return false, err + } + return filter.Match(filterable), nil + } + grantsHash, err := authResults.GrantsHash(ctx) + if err != nil { + return nil, err + } - if outputFields.Has(globals.ScopeField) { - outputOpts = append(outputOpts, handlers.WithScope(authzScopes[item.GetProjectId()])) + var listResp *pagination.ListResponse[target.Target] + if req.GetRefreshToken() == "" { + listResp, err = target.List(ctx, grantsHash, pageSize, filterItemFn, repo) + if err != nil { + return nil, err } - if outputFields.Has(globals.AuthorizedActionsField) { - authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&pr)).Strings() - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } else { + rt, err := handlers.ParseRefreshToken(ctx, req.GetRefreshToken()) + if err != nil { + return nil, err } - - item, err := toProto(ctx, item, outputOpts...) + // We're doing the conversion from the protobuf types to the + // domain types here rather than in the domain so that the domain + // doesn't need to know about the protobuf types. + domainRefreshToken, err := refreshtoken.New( + ctx, + rt.CreatedTime.AsTime(), + rt.UpdatedTime.AsTime(), + handlers.RefreshTokenResourceToResource(rt.ResourceType), + rt.GrantsHash, + rt.LastItemId, + rt.LastItemUpdatedTime.AsTime(), + ) if err != nil { return nil, err } + if err := domainRefreshToken.Validate(ctx, resource.Target, grantsHash); err != nil { + return nil, err + } + listResp, err = target.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, domainRefreshToken, repo) + if err != nil { + return nil, err + } + } - filterable, err := subtypes.Filterable(item) + finalItems := make([]*pb.Target, 0, len(listResp.Items)) + for _, item := range listResp.Items { + item, err := toProto(ctx, item, newOutputOpts(ctx, item, authResults, authzScopes)...) if err != nil { return nil, err } - if filter.Match(filterable) { - finalItems = append(finalItems, item) + finalItems = append(finalItems, item) + } + respType := "delta" + if listResp.CompleteListing { + respType = "complete" + } + resp := &pbs.ListTargetsResponse{ + Items: finalItems, + EstItemCount: uint32(listResp.EstimatedItemCount), + RemovedIds: listResp.DeletedIds, + ResponseType: respType, + SortBy: "updated_time", + SortDir: "asc", + } + + if listResp.RefreshToken != nil { + if listResp.RefreshToken.ResourceType != resource.Target { + return nil, errors.New(ctx, errors.Internal, op, "refresh token resource type does not match service resource type") + } + resp.RefreshToken, err = handlers.MarshalRefreshToken(ctx, &pbs.ListRefreshToken{ + CreatedTime: timestamppb.New(listResp.RefreshToken.CreatedTime), + UpdatedTime: timestamppb.New(listResp.RefreshToken.UpdatedTime), + ResourceType: pbs.ResourceType_RESOURCE_TYPE_TARGET, + GrantsHash: listResp.RefreshToken.GrantsHash, + LastItemId: listResp.RefreshToken.LastItemId, + LastItemUpdatedTime: timestamppb.New(listResp.RefreshToken.LastItemUpdatedTime), + }) + if err != nil { + return nil, err } } - return &pbs.ListTargetsResponse{Items: finalItems}, nil + return resp, nil } // GetTarget implements the interface pbs.TargetServiceServer. @@ -1293,18 +1366,6 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { return rows > 0, nil } -func (s Service) listFromRepo(ctx context.Context, perms []perms.Permission) ([]target.Target, error) { - repo, err := s.repoFn(target.WithPermissions(perms)) - if err != nil { - return nil, err - } - ul, err := repo.ListTargets(ctx, target.WithLimit(-1)) - if err != nil { - return nil, err - } - return ul, nil -} - func (s Service) addHostSourcesInRepo(ctx context.Context, targetId string, hostSourceIds []string, version uint32) (target.Target, error) { repo, err := s.repoFn() if err != nil { @@ -1826,6 +1887,23 @@ func validateListRequest(ctx context.Context, req *pbs.ListTargetsRequest) error return nil } +func newOutputOpts(ctx context.Context, item target.Target, authResults auth.VerifyResults, authzScopes map[string]*scopes.ScopeInfo) []handlers.Option { + pr := perms.Resource{Id: item.GetPublicId(), ScopeId: item.GetProjectId(), Type: resource.Target} + outputFields := authResults.FetchOutputFields(pr, action.List).SelfOrDefaults(authResults.UserId) + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authzScopes[item.GetProjectId()])) + } + if outputFields.Has(globals.AuthorizedActionsField) { + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&pr)).Strings() + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } + return outputOpts +} + func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) { diff --git a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go index d7d64a15abe..65a7764c974 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -113,7 +113,7 @@ func testService(t *testing.T, ctx context.Context, conn *db.DB, kms *kms.Kms, w staticCredRepoFn := func() (*credstatic.Repository, error) { return credstatic.NewRepository(context.Background(), rw, rw, kms) } - return targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, nil) + return targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, 1000, nil) } func TestGet(t *testing.T) { @@ -346,6 +346,11 @@ func TestList(t *testing.T) { req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true}, res: &pbs.ListTargetsResponse{Items: totalTars}, }, + { + name: "Paginate listing", + req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true, PageSize: 2}, + res: &pbs.ListTargetsResponse{Items: totalTars[:2]}, + }, { name: "Filter To Many Targets", req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, proj.GetPublicId())}, @@ -413,6 +418,250 @@ func TestList(t *testing.T) { } } +func TestListPagination(t *testing.T) { + t.Parallel() + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + sqlDB, err := conn.SqlDB(ctx) + require.NoError(t, err) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + tokenRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(ctx, rw, rw, kms) + } + serversRepoFn := func() (*server.Repository, error) { + return server.NewRepository(ctx, rw, rw, kms) + } + repo, err := target.NewRepository(ctx, rw, rw, kms) + require.NoError(t, err) + + org, proj := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + r := iam.TestRole(t, conn, proj.GetPublicId()) + _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) + _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + hss := static.TestSets(t, conn, hc.GetPublicId(), 2) + s, err := testService(t, context.Background(), conn, kms, wrapper) + require.NoError(t, err) + + var allTargets []*pb.Target + for i := 0; i < 10; i++ { + tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), fmt.Sprintf("tar%d", i), target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) + allTargets = append(allTargets, &pb.Target{ + Id: tar.GetPublicId(), + ScopeId: proj.GetPublicId(), + Name: wrapperspb.String(tar.GetName()), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + CreatedTime: tar.GetCreateTime().GetTimestamp(), + UpdatedTime: tar.GetUpdateTime().GetTimestamp(), + Version: tar.GetVersion(), + Type: tcp.Subtype.String(), + Attrs: &pb.Target_TcpTargetAttributes{}, + SessionMaxSeconds: wrapperspb.UInt32(28800), + SessionConnectionLimit: wrapperspb.Int32(-1), + AuthorizedActions: testAuthorizedActions, + Address: &wrapperspb.StringValue{}, + }) + } + + // Run analyze to update postgres estimates + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + // Test without anon user + requestInfo := authpb.RequestInfo{ + TokenFormat: uint32(auth.AuthTokenTypeBearer), + PublicId: at.GetPublicId(), + Token: at.GetToken(), + } + requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) + ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) + + // Start paginating, recursively + req := &pbs.ListTargetsRequest{ + ScopeId: "global", + Recursive: true, + Filter: "", + RefreshToken: "", + PageSize: 2, + } + got, err := s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + // Compare without comparing the refresh token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: allTargets[0:2], + ResponseType: "delta", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) + + // Request second page + req.RefreshToken = got.RefreshToken + got, err = s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + // Compare without comparing the refresh token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: allTargets[2:4], + ResponseType: "delta", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) + + // Request rest of results + req.RefreshToken = got.RefreshToken + req.PageSize = 10 + got, err = s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 6) + // Compare without comparing the refresh token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: allTargets[4:], + ResponseType: "complete", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) + + // Create another target + tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test-target", target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) + newTarget := &pb.Target{ + Id: tar.GetPublicId(), + ScopeId: proj.GetPublicId(), + Name: wrapperspb.String(tar.GetName()), + Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, + CreatedTime: tar.GetCreateTime().GetTimestamp(), + UpdatedTime: tar.GetUpdateTime().GetTimestamp(), + Version: tar.GetVersion(), + Type: tcp.Subtype.String(), + Attrs: &pb.Target_TcpTargetAttributes{}, + SessionMaxSeconds: wrapperspb.UInt32(28800), + SessionConnectionLimit: wrapperspb.Int32(-1), + AuthorizedActions: testAuthorizedActions, + Address: &wrapperspb.StringValue{}, + } + allTargets = append(allTargets, newTarget) + + // Delete one of the other targets + _, err = repo.DeleteTarget(ctx, allTargets[0].Id) + require.NoError(t, err) + deletedTarget := allTargets[0] + allTargets = allTargets[1:] + + // Run analyze to update postgres estimates + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + // Request updated results + req.RefreshToken = got.RefreshToken + got, err = s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + // Compare without comparing the refresh token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: allTargets[len(allTargets)-1:], + ResponseType: "complete", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + // Should contain the deleted target + RemovedIds: []string{deletedTarget.Id}, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) + + // Request new page with filter requiring looping + // to fill the page. + req.RefreshToken = "" + req.PageSize = 1 + req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, allTargets[len(allTargets)-2].Id, allTargets[len(allTargets)-1].Id) + got, err = s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: []*pb.Target{allTargets[len(allTargets)-2]}, + ResponseType: "delta", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + // Should be empty again + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) + req.RefreshToken = got.RefreshToken + // Get the second page + got, err = s.ListTargets(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListTargetsResponse{ + Items: []*pb.Target{allTargets[len(allTargets)-1]}, + ResponseType: "complete", + RefreshToken: "", + SortBy: "updated_time", + SortDir: "asc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "refresh_token"), + ), + ) +} + func TestDelete(t *testing.T) { t.Parallel() ctx := context.Background() @@ -2594,7 +2843,7 @@ func TestAuthorizeSession(t *testing.T) { statusGracePeriod := new(atomic.Int64) statusGracePeriod.Store(int64(server.DefaultLiveness)) - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, nil) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, 1000, nil) require.NoError(t, err) for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -2767,7 +3016,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { statusGracePeriod := new(atomic.Int64) statusGracePeriod.Store(int64(server.DefaultLiveness)) - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, nil) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, 1000, nil) require.NoError(t, err) hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] @@ -3343,7 +3592,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { statusGracePeriod := new(atomic.Int64) statusGracePeriod.Store(int64(server.DefaultLiveness)) - s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, nil) + s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, nil, statusGracePeriod, 1000, nil) require.NoError(t, err) // Authorized user gets full permissions @@ -3410,7 +3659,7 @@ func TestAuthorizeSession_Errors(t *testing.T) { h.Address = fmt.Sprintf("%s:54321", h.GetAddress()) repo, err := staticHostRepoFn() require.NoError(t, err) - h, _, err = repo.UpdateHost(ctx, hc.GetProjectId(), h, h.GetVersion(), []string{"address"}) + _, _, err = repo.UpdateHost(ctx, hc.GetProjectId(), h, h.GetVersion(), []string{"address"}) require.NoError(t, err) return apiTar.GetItem().GetVersion() } diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index ae41291c9fe..d423490b43e 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -3176,6 +3176,21 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "refresh_token", + "description": "An opaque token used to continue an existing iteration or\nrequest updated items. If not specified, pagination\nwill start from the beginning.", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "page_size", + "description": "The maximum size of a page in this iteration.\nIf unset, the default page size configured will be used.\nIf the page_size is greater than the default page configured,\nan error will be returned.", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" } ], "tags": [ @@ -7684,6 +7699,34 @@ "type": "object", "$ref": "#/definitions/controller.api.resources.targets.v1.Target" } + }, + "response_type": { + "type": "string", + "description": "The type of response, either \"delta\" or \"complete\".\nDelta signifies that this is part of a paginated result\nor an update to a previously completed pagination.\nComplete signifies that it is the last page." + }, + "refresh_token": { + "type": "string", + "description": "An opaque token used to continue an existing pagination or\nrequest updated items. Use this token in the next list request\nto request the next page." + }, + "sort_by": { + "type": "string", + "description": "The name of the field which the items are sorted by." + }, + "sort_dir": { + "type": "string", + "description": "The direction of the sort, either \"asc\" or \"desc\"." + }, + "removed_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of item IDs that have been removed since they were returned\nas part of a pagination. They should be dropped from any client cache.\nThis may contain items that are not known to the cache, if they were\ncreated and deleted between listings." + }, + "est_item_count": { + "type": "integer", + "format": "int64", + "description": "An estimate at the total items available. This may change during pagination." } } }, diff --git a/internal/gen/controller/api/services/target_service.pb.go b/internal/gen/controller/api/services/target_service.pb.go index 1f5fbd50b8c..b30ac8b56f1 100644 --- a/internal/gen/controller/api/services/target_service.pb.go +++ b/internal/gen/controller/api/services/target_service.pb.go @@ -130,6 +130,15 @@ type ListTargetsRequest struct { ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"public"` // @gotags: `class:"public"` + // An opaque token used to continue an existing iteration or + // request updated items. If not specified, pagination + // will start from the beginning. + RefreshToken string `protobuf:"bytes,40,opt,name=refresh_token,proto3" json:"refresh_token,omitempty" class:"public"` // @gotags: `class:"public"` + // The maximum size of a page in this iteration. + // If unset, the default page size configured will be used. + // If the page_size is greater than the default page configured, + // an error will be returned. + PageSize uint32 `protobuf:"varint,50,opt,name=page_size,proto3" json:"page_size,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *ListTargetsRequest) Reset() { @@ -185,12 +194,46 @@ func (x *ListTargetsRequest) GetFilter() string { return "" } +func (x *ListTargetsRequest) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +func (x *ListTargetsRequest) GetPageSize() uint32 { + if x != nil { + return x.PageSize + } + return 0 +} + type ListTargetsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Items []*targets.Target `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // The type of response, either "delta" or "complete". + // Delta signifies that this is part of a paginated result + // or an update to a previously completed pagination. + // Complete signifies that it is the last page. + ResponseType string `protobuf:"bytes,2,opt,name=response_type,proto3" json:"response_type,omitempty" class:"public"` // @gotags: `class:"public"` + // An opaque token used to continue an existing pagination or + // request updated items. Use this token in the next list request + // to request the next page. + RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,proto3" json:"refresh_token,omitempty" class:"public"` // @gotags: `class:"public"` + // The name of the field which the items are sorted by. + SortBy string `protobuf:"bytes,4,opt,name=sort_by,proto3" json:"sort_by,omitempty" class:"public"` // @gotags: `class:"public"` + // The direction of the sort, either "asc" or "desc". + SortDir string `protobuf:"bytes,5,opt,name=sort_dir,proto3" json:"sort_dir,omitempty" class:"public"` // @gotags: `class:"public"` + // A list of item IDs that have been removed since they were returned + // as part of a pagination. They should be dropped from any client cache. + // This may contain items that are not known to the cache, if they were + // created and deleted between listings. + RemovedIds []string `protobuf:"bytes,6,rep,name=removed_ids,proto3" json:"removed_ids,omitempty" class:"public"` // @gotags: `class:"public"` + // An estimate at the total items available. This may change during pagination. + EstItemCount uint32 `protobuf:"varint,7,opt,name=est_item_count,proto3" json:"est_item_count,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *ListTargetsResponse) Reset() { @@ -232,6 +275,48 @@ func (x *ListTargetsResponse) GetItems() []*targets.Target { return nil } +func (x *ListTargetsResponse) GetResponseType() string { + if x != nil { + return x.ResponseType + } + return "" +} + +func (x *ListTargetsResponse) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +func (x *ListTargetsResponse) GetSortBy() string { + if x != nil { + return x.SortBy + } + return "" +} + +func (x *ListTargetsResponse) GetSortDir() string { + if x != nil { + return x.SortDir + } + return "" +} + +func (x *ListTargetsResponse) GetRemovedIds() []string { + if x != nil { + return x.RemovedIds + } + return nil +} + +func (x *ListTargetsResponse) GetEstItemCount() uint32 { + if x != nil { + return x.EstItemCount + } + return 0 +} + type CreateTargetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1398,363 +1483,381 @@ var file_controller_api_services_v1_target_service_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x65, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, - 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, - 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, - 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x58, 0x0a, 0x13, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x56, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa9, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, + 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, + 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, + 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x28, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x32, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x22, 0xa4, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, + 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, + 0x62, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x12, 0x20, + 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, + 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, + 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x56, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x69, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa4, 0x01, 0x0a, 0x13, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, + 0x61, 0x73, 0x6b, 0x18, 0x03, 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, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, + 0x73, 0x6b, 0x22, 0x57, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x25, 0x0a, 0x13, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x1b, 0x41, 0x64, + 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x5f, 0x0a, + 0x1c, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x69, - 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa4, 0x01, 0x0a, 0x13, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x71, + 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x73, 0x22, 0x5f, 0x0a, 0x1c, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, - 0x6b, 0x18, 0x03, 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, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, - 0x22, 0x57, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x25, 0x0a, 0x13, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x22, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x5f, 0x0a, 0x1c, 0x41, - 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, + 0x65, 0x6d, 0x22, 0x74, 0x0a, 0x1e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, + 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x62, 0x0a, 0x1f, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x71, 0x0a, 0x1b, - 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, - 0x5f, 0x0a, 0x1c, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0x74, 0x0a, 0x1e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xc2, 0x02, 0x0a, + 0x21, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x62, 0x0a, 0x1f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xc2, 0x02, 0x0a, 0x21, 0x41, - 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, 0x62, 0x72, - 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x73, 0x12, 0x5e, 0x0a, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, + 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x73, 0x12, 0x5e, 0x0a, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1c, - 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, - 0x65, 0x0a, 0x22, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xc2, 0x02, 0x0a, 0x21, 0x53, 0x65, 0x74, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, - 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1e, - 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x5e, - 0x0a, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, + 0x64, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x52, 0x1c, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x73, 0x22, 0x65, 0x0a, 0x22, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xc2, 0x02, 0x0a, 0x21, 0x53, 0x65, 0x74, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, 0x62, 0x72, 0x6f, 0x6b, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, + 0x12, 0x5e, 0x0a, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x4a, 0x04, - 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1c, 0x65, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x65, 0x0a, 0x22, 0x53, - 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x22, 0xc5, 0x02, 0x0a, 0x24, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, - 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1e, 0x62, - 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x5e, 0x0a, - 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, + 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1c, 0x65, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x4a, 0x04, 0x08, - 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1c, 0x65, 0x67, 0x72, 0x65, 0x73, - 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x68, 0x0a, 0x25, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x65, 0x0a, + 0x22, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x17, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x69, 0x0a, 0x18, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x39, 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, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x32, 0x98, 0x15, 0x0a, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa2, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0xc5, 0x02, 0x0a, 0x24, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, + 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x1e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x12, + 0x5e, 0x0a, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x2a, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x1c, 0x65, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x52, 0x21, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x68, 0x0a, 0x25, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x17, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x69, 0x0a, 0x18, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 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, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x98, 0x15, 0x0a, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa2, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x38, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x18, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x9a, 0x01, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 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, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 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, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x14, - 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0xaf, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 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, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, 0x41, 0x1a, - 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, - 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, - 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x0b, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0xad, 0x01, 0x0a, 0x0c, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x18, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x9a, 0x01, 0x0a, + 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 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, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 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, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, + 0x41, 0x14, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x76, + 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0xaf, 0x01, 0x0a, 0x0c, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 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, 0x54, 0x61, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, - 0x41, 0x13, 0x12, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xa1, 0x01, 0x0a, 0x0c, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x92, + 0x41, 0x1a, 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x19, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x0b, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0xad, 0x01, 0x0a, 0x0c, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 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, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x3a, 0x92, 0x41, 0x13, 0x12, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xa1, 0x01, 0x0a, 0x0c, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2f, 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, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x2e, 0x92, 0x41, 0x13, 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, + 0xcf, 0x01, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x50, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x73, + 0x20, 0x61, 0x20, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x30, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x3d, 0x2a, 0x2a, 0x7d, 0x3a, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0xa7, 0x02, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, + 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x37, 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, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x92, - 0x41, 0x13, 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcf, 0x01, - 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x33, 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, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x92, - 0x41, 0x17, 0x12, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x73, 0x20, 0x61, - 0x20, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, - 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x3d, 0x2a, 0x2a, 0x7d, 0x3a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0xa7, 0x02, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, - 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x37, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x38, 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, 0x41, - 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x01, 0x92, 0x41, - 0x66, 0x12, 0x64, 0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, - 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, 0x6f, - 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x20, 0x43, 0x61, 0x6e, 0x6e, 0x6f, - 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, - 0x68, 0x65, 0x69, 0x72, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x20, 0x73, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, - 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68, 0x6f, 0x73, - 0x74, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0xa7, 0x02, 0x0a, 0x14, 0x53, 0x65, - 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x37, 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, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x01, + 0x92, 0x41, 0x66, 0x12, 0x64, 0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, + 0x74, 0x6f, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x20, 0x43, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x73, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, + 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68, + 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0xa7, 0x02, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x01, 0x92, 0x41, 0x66, 0x12, 0x64, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x2e, 0x20, 0x43, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, - 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x20, 0x74, - 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x73, 0x65, 0x74, - 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, - 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0xf3, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, - 0x3a, 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, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x92, 0x41, 0x27, 0x12, 0x25, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, - 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x68, 0x6f, 0x73, - 0x74, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x87, 0x02, 0x0a, 0x1a, 0x41, 0x64, - 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3d, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6a, 0x92, 0x41, 0x2f, 0x12, 0x2d, 0x41, 0x64, - 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, - 0x6f, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x32, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, - 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x84, 0x02, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x72, 0x63, 0x65, 0x73, 0x12, 0x37, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x53, 0x65, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9b, 0x01, 0x92, 0x41, 0x66, 0x12, 0x64, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x20, 0x43, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, + 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x73, + 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, + 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0xf3, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x12, 0x3a, 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, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x92, 0x41, 0x27, 0x12, + 0x25, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x62, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x68, + 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x87, 0x02, 0x0a, 0x1a, + 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3d, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x3d, 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, - 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x3e, 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, 0x53, - 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x67, 0x92, 0x41, 0x2c, 0x12, 0x2a, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x91, 0x02, 0x0a, 0x1d, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x40, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6a, 0x92, 0x41, 0x2f, 0x12, 0x2d, + 0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x32, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x27, 0x2f, 0x76, + 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, + 0x64, 0x64, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x84, 0x02, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x12, 0x3d, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x3e, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x67, 0x92, 0x41, 0x2c, 0x12, 0x2a, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, + 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x91, 0x02, 0x0a, + 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x40, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x6b, 0x92, 0x41, 0x2d, 0x12, 0x2b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x22, 0x2a, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, - 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x63, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x57, - 0xa2, 0xe3, 0x29, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 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, + 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x41, 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, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x6b, 0x92, 0x41, 0x2d, 0x12, 0x2b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x73, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x01, 0x2a, 0x62, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2a, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x42, 0x57, 0xa2, 0xe3, 0x29, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 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 ( diff --git a/internal/proto/controller/api/services/v1/target_service.proto b/internal/proto/controller/api/services/v1/target_service.proto index 809b52b6373..c4710271983 100644 --- a/internal/proto/controller/api/services/v1/target_service.proto +++ b/internal/proto/controller/api/services/v1/target_service.proto @@ -198,10 +198,39 @@ message ListTargetsRequest { string scope_id = 1; // @gotags: `class:"public" eventstream:"observation"` bool recursive = 20 [json_name = "recursive"]; // @gotags: `class:"public" eventstream:"observation"` string filter = 30 [json_name = "filter"]; // @gotags: `class:"public"` + // An opaque token used to continue an existing iteration or + // request updated items. If not specified, pagination + // will start from the beginning. + string refresh_token = 40 [json_name = "refresh_token"]; // @gotags: `class:"public"` + // The maximum size of a page in this iteration. + // If unset, the default page size configured will be used. + // If the page_size is greater than the default page configured, + // an error will be returned. + uint32 page_size = 50 [json_name = "page_size"]; // @gotags: `class:"public"` } message ListTargetsResponse { repeated resources.targets.v1.Target items = 1; + // The type of response, either "delta" or "complete". + // Delta signifies that this is part of a paginated result + // or an update to a previously completed pagination. + // Complete signifies that it is the last page. + string response_type = 2 [json_name = "response_type"]; // @gotags: `class:"public"` + // An opaque token used to continue an existing pagination or + // request updated items. Use this token in the next list request + // to request the next page. + string refresh_token = 3 [json_name = "refresh_token"]; // @gotags: `class:"public"` + // The name of the field which the items are sorted by. + string sort_by = 4 [json_name = "sort_by"]; // @gotags: `class:"public"` + // The direction of the sort, either "asc" or "desc". + string sort_dir = 5 [json_name = "sort_dir"]; // @gotags: `class:"public"` + // A list of item IDs that have been removed since they were returned + // as part of a pagination. They should be dropped from any client cache. + // This may contain items that are not known to the cache, if they were + // created and deleted between listings. + repeated string removed_ids = 6 [json_name = "removed_ids"]; // @gotags: `class:"public"` + // An estimate at the total items available. This may change during pagination. + uint32 est_item_count = 7 [json_name = "est_item_count"]; // @gotags: `class:"public"` } message CreateTargetRequest { From 85b96514fed5bf8bad4f50a58c92a55ef7f59bec Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Tue, 24 Oct 2023 16:36:10 -0700 Subject: [PATCH 4/4] internal/tests: add target pagination test --- internal/tests/api/targets/target_test.go | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/internal/tests/api/targets/target_test.go b/internal/tests/api/targets/target_test.go index 508b01a8b5f..298cf7b8b46 100644 --- a/internal/tests/api/targets/target_test.go +++ b/internal/tests/api/targets/target_test.go @@ -242,6 +242,43 @@ func TestList(t *testing.T) { assert.Equal(filterItem.Id, ul.Items[0].Id) } +func TestListWithRefreshToken(t *testing.T) { + require := require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + tarClient := targets.NewClient(client) + _, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("1"), targets.WithTcpTargetDefaultPort(2)) + require.NoError(err) + _, err = tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("2"), targets.WithTcpTargetDefaultPort(2)) + require.NoError(err) + + // Refresh tokens using project scope + res, err := tarClient.List(tc.Context(), proj.GetPublicId(), targets.WithRecursive(true)) + require.NoError(err) + require.Len(res.Items, 2, "expected the 2 targets created above") + refTok := res.RefreshToken + + res, err = tarClient.List(tc.Context(), proj.GetPublicId(), targets.WithRecursive(true), targets.WithRefreshToken(refTok)) + require.NoError(err) + require.Empty(res.Items) + + // Refresh tokens recursive listing over global scope + res, err = tarClient.List(tc.Context(), "global", targets.WithRecursive(true)) + require.NoError(err) + require.Len(res.Items, 4, "expected the 2 targets created above and the 2 auto created for the test controller") + refTok = res.RefreshToken + + res, err = tarClient.List(tc.Context(), "global", targets.WithRecursive(true), targets.WithRefreshToken(refTok)) + require.NoError(err) + require.Empty(res.Items) +} + func TestTarget_AddressMutualExclusiveRelationship(t *testing.T) { tc := controller.NewTestController(t, nil)