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.
This commit is contained in:
1 parent
9ce5a14ef0
commit
ef3bdfd5eb
39 files changed
+2139
-41
No files matched your search
@@ -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.
|
||||
|
||||
+95
-2
@@ -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.
|
||||
|
||||
+95
-2
@@ -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.
|
||||
|
||||
+95
-2
@@ -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.
|
||||
|
||||
+95
-2
@@ -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.
|
||||
|
||||
+95
-2
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user