From ef3bdfd5ebf24e8182b3f52cab12619c33d85b76 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Mon, 23 Oct 2023 16:49:32 -0700 Subject: [PATCH] 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/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 +++++++++++++++++- 39 files changed, 2139 insertions(+), 41 deletions(-) diff --git a/api/accounts/account.gen.go b/api/accounts/account.gen.go index 60c694438d..8ec3e77349 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 7d6ef19552..c2c69dfd1e 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 51369be296..4f303ddb58 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 a66082e467..98b8af5875 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 d0fee9dbdf..0a458cbd40 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 de4d894d4e..24492f20d2 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 6a43b5e154..e4a352684d 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 37deb83dd8..8088153c90 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 c0df55609b..bf82cccd6d 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 201ae595cd..b403574144 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 a0dbb8a924..b7b241627b 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 4b590b6d9c..13e8443187 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/groups/group.gen.go b/api/groups/group.gen.go index 80a6e950a5..be799ee68e 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 c6259f86ad..70e93d0b28 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 7776ec6913..7625539c2d 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 5ef7ca49e4..fe80ca344a 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 3758db64c3..11eeea429d 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 6e8da1836f..532b5884d6 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 e4ca5850bc..19528c9fce 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 e17f8c745a..7d2fa8f8bf 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 be4fb3d727..5588c29188 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 82e2c7ad7c..87c9cdaea9 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 a2ae657848..b0010a4832 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 33e189f94b..ae497a7d5c 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 a28f2493d0..10ae82e7f1 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 b71068fdfa..ac3da24a9f 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 34ea486e98..aaf4608048 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 90c908c229..1ea1f8028f 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 4f91a4c518..517869fb9c 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 03916ad06e..b19149f128 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 1a388de5d4..7c28e93224 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 1558604cfa..b1563d92a5 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 ba4ac8ce92..b34abbb491 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 473dc9231c..5b1b3a2d26 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 0762d1b35b..18092d471f 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 6df55ba96c..df14657931 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 bdaa572247..3bdc8f732e 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 8d6743e771..7b9bd38196 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 f39fdd3b44..6ea904a547 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -265,6 +265,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 } `)) @@ -601,10 +673,9 @@ package {{ .Package }} import ( "context" "fmt" + "slices" "time" - "github.com/kr/pretty" - "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/scopes" ) @@ -651,7 +722,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 } @@ -659,6 +734,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 } @@ -717,6 +808,7 @@ type options struct { withAutomaticVersioning bool withSkipCurlOutput bool withFilter string + withRefreshToken string {{ if .RecursiveListing }} withRecursive bool {{ end }} } @@ -740,6 +832,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) @@ -767,6 +862,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