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.
pull/4202/head
Johan Brandhorst-Satzkorn 3 years ago
parent 9ce5a14ef0
commit ef3bdfd5eb

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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 {

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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.

@ -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
}

@ -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

Loading…
Cancel
Save