Add pagination for iam Roles (#4161)

* Add pagination for iam Roles
This commit is contained in:
Todd authored and Johan Brandhorst-Satzkorn committed 2024-01-05 07:43:21 -08:00
1 parent 9ebb69f2a4
commit 5aa560387c
21 files changed
+2248 -341

No files matched your search

+6 -3
View File
@@ -632,14 +632,17 @@ func (b *Server) RegisterPlugin(ctx context.Context, name string, hostClient plg
// tokens. It also creates a role with an `authorize-session` grant for the
// provided targetId.
func unprivilegedDevUserRoleSetup(ctx context.Context, repo *iam.Repository, userId, projectId, targetId string) error {
roles, err := repo.ListRoles(ctx, []string{projectId})
noopFilter := func(ctx context.Context, item *iam.Role) (bool, error) {
return true, nil
}
roles, err := iam.ListRoles(ctx, []byte("dummy grant"), globals.DefaultMaxPageSize, noopFilter, repo, []string{projectId})
if err != nil {
return fmt.Errorf("failed to list existing roles for project id %q: %w", projectId, err)
}
// Look for default grants role to set unprivileged user as a principal.
defaultRoleIdx := -1
for i, r := range roles {
for i, r := range roles.Items {
// Hacky, I know, but saves a DB trip to look up other
// characteristics like "if any principals are currently attached".
// No matter what we pick here it's a bit heuristical.
@@ -651,7 +654,7 @@ func unprivilegedDevUserRoleSetup(ctx context.Context, repo *iam.Repository, use
if defaultRoleIdx == -1 {
return fmt.Errorf("couldn't find default grants role for project id %q", projectId)
}
defaultRole := roles[defaultRoleIdx]
defaultRole := roles.Items[defaultRoleIdx]
// This function may be called more than once for the same boundary
// deployment (eg: if we're creating more than one target), so we need to
+1 -1
View File
@@ -245,7 +245,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
services.RegisterGroupServiceServer(s, gs)
}
if _, ok := currentServices[services.RoleService_ServiceDesc.ServiceName]; !ok {
rs, err := roles.NewService(c.baseContext, c.IamRepoFn)
rs, err := roles.NewService(c.baseContext, c.IamRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
if err != nil {
return fmt.Errorf("failed to create role handler service: %w", err)
}
@@ -16,12 +16,15 @@ import (
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/boundary/version"
"github.com/hashicorp/go-secure-stdlib/strutil"
"google.golang.org/grpc/codes"
@@ -72,22 +75,28 @@ func init() {
type Service struct {
pbs.UnsafeRoleServiceServer
repoFn common.IamRepoFactory
repoFn common.IamRepoFactory
maxPageSize uint
}
var _ pbs.RoleServiceServer = (*Service)(nil)
// NewService returns a role service which handles role related requests to boundary.
func NewService(ctx context.Context, repo common.IamRepoFactory) (Service, error) {
func NewService(ctx context.Context, repo common.IamRepoFactory, maxPageSize uint) (Service, error) {
const op = "roles.NewService"
if repo == nil {
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository")
}
return Service{repoFn: repo}, nil
if maxPageSize == 0 {
maxPageSize = uint(globals.DefaultMaxPageSize)
}
return Service{repoFn: repo, maxPageSize: maxPageSize}, nil
}
// ListRoles implements the interface pbs.RoleServiceServer.
func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs.ListRolesResponse, error) {
const op = "roles.(Service).ListRoles"
if err := validateListRequest(ctx, req); err != nil {
return nil, err
}
@@ -115,50 +124,117 @@ func (s Service) ListRoles(ctx context.Context, req *pbs.ListRolesRequest) (*pbs
return &pbs.ListRolesResponse{}, nil
}
items, err := s.listFromRepo(ctx, scopeIds)
if err != nil {
return nil, err
}
if len(items) == 0 {
return &pbs.ListRolesResponse{}, nil
pageSize := int(s.maxPageSize)
// Use the requested page size only if it is smaller than
// the configured max.
if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize {
pageSize = int(req.GetPageSize())
}
filter, err := handlers.NewFilter(ctx, req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Role, 0, len(items))
res := perms.Resource{
Type: resource.Role,
}
for _, item := range items {
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
continue
}
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(ctx, item, nil, nil, outputOpts...)
var filterItemFn func(ctx context.Context, item *iam.Role) (bool, error)
switch {
case req.GetFilter() != "":
// Only use a filter if we need to
filter, err := handlers.NewFilter(ctx, req.GetFilter())
if err != nil {
return nil, err
}
if filter.Match(item) {
finalItems = append(finalItems, item)
filterItemFn = func(ctx context.Context, item *iam.Role) (bool, error) {
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
if !ok {
return false, nil
}
pbItem, err := toProto(ctx, item, nil, nil, outputOpts...)
if err != nil {
return false, err
}
return filter.Match(pbItem), nil
}
default:
filterItemFn = func(ctx context.Context, item *iam.Role) (bool, error) {
return true, nil
}
}
return &pbs.ListRolesResponse{Items: finalItems}, nil
grantsHash, err := authResults.GrantsHash(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var listResp *pagination.ListResponse[*iam.Role]
var sortBy string
if req.GetListToken() == "" {
sortBy = "created_time"
listResp, err = iam.ListRoles(ctx, grantsHash, pageSize, filterItemFn, repo, scopeIds)
if err != nil {
return nil, err
}
} else {
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Role, grantsHash)
if err != nil {
return nil, err
}
switch st := listToken.Subtype.(type) {
case *listtoken.PaginationToken:
sortBy = "created_time"
listResp, err = iam.ListRolesPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
if err != nil {
return nil, err
}
case *listtoken.StartRefreshToken:
sortBy = "updated_time"
listResp, err = iam.ListRolesRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
if err != nil {
return nil, err
}
case *listtoken.RefreshToken:
sortBy = "updated_time"
listResp, err = iam.ListRolesRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
if err != nil {
return nil, err
}
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
}
}
finalItems := make([]*pb.Role, 0, len(listResp.Items))
for _, item := range listResp.Items {
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
if !ok {
continue
}
item, err := toProto(ctx, item, nil, nil, outputOpts...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
finalItems = append(finalItems, item)
}
respType := "delta"
if listResp.CompleteListing {
respType = "complete"
}
resp := &pbs.ListRolesResponse{
Items: finalItems,
EstItemCount: uint32(listResp.EstimatedItemCount),
RemovedIds: listResp.DeletedIds,
ResponseType: respType,
SortBy: sortBy,
SortDir: "desc",
}
if listResp.ListToken != nil {
resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_ROLE)
if err != nil {
return nil, err
}
}
return resp, nil
}
// GetRoles implements the interface pbs.RoleServiceServer.
@@ -620,18 +696,6 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
return rows > 0, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.Role, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
}
rl, err := repo.ListRoles(ctx, scopeIds, iam.WithLimit(-1))
if err != nil {
return nil, err
}
return rl, nil
}
func (s Service) addPrinciplesInRepo(ctx context.Context, roleId string, principalIds []string, version uint32) (*iam.Role, []*iam.PrincipalRole, []*iam.RoleGrant, error) {
const op = "roles.(Service).addPrincpleInRepo"
repo, err := s.repoFn()
@@ -1159,3 +1223,26 @@ func validateRemoveRoleGrantsRequest(ctx context.Context, req *pbs.RemoveRoleGra
}
return nil
}
func newOutputOpts(ctx context.Context, item *iam.Role, scopeInfoMap map[string]*scopes.ScopeInfo, authResults auth.VerifyResults) ([]handlers.Option, bool) {
res := perms.Resource{
Type: resource.Role,
}
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res))
if len(authorizedActions) == 0 {
return nil, false
}
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
}
return outputOpts, true
}
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"slices"
"strings"
"testing"
@@ -15,14 +16,19 @@ import (
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/ldap"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/roles"
"github.com/hashicorp/boundary/internal/db"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/roles"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
@@ -178,7 +184,7 @@ func TestGet(t *testing.T) {
req := proto.Clone(toMerge).(*pbs.GetRoleRequest)
proto.Merge(req, tc.req)
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(err, "Couldn't create new role service.")
got, gErr := s.GetRole(auth.DisabledAuthTestContext(repoFn, tc.scopeId), req)
@@ -199,6 +205,7 @@ func TestGet(t *testing.T) {
}
func TestList(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
iamRepo := iam.TestRepo(t, conn, wrap)
@@ -237,6 +244,16 @@ func TestList(t *testing.T) {
totalRoles = append(totalRoles, wantProjRoles[i])
}
slices.Reverse(wantOrgRoles)
slices.Reverse(wantProjRoles)
slices.Reverse(totalRoles)
// Run analyze to update postgres estimates
sqlDb, err := conn.SqlDB(ctx)
require.NoError(t, err)
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(t, err)
cases := []struct {
name string
req *pbs.ListRolesRequest
@@ -246,58 +263,117 @@ func TestList(t *testing.T) {
{
name: "List Many Role",
req: &pbs.ListRolesRequest{ScopeId: oWithRoles.GetPublicId()},
res: &pbs.ListRolesResponse{Items: wantOrgRoles},
res: &pbs.ListRolesResponse{
Items: wantOrgRoles,
EstItemCount: uint32(len(wantOrgRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List No Roles",
req: &pbs.ListRolesRequest{ScopeId: oNoRoles.GetPublicId()},
res: &pbs.ListRolesResponse{},
res: &pbs.ListRolesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Paginate listing",
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true, PageSize: 2},
res: &pbs.ListRolesResponse{
Items: totalRoles[:2],
EstItemCount: uint32(len(totalRoles)),
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List Many Project Role",
req: &pbs.ListRolesRequest{ScopeId: pWithRoles.GetPublicId()},
res: &pbs.ListRolesResponse{Items: wantProjRoles},
res: &pbs.ListRolesResponse{
Items: wantProjRoles,
EstItemCount: uint32(len(wantProjRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List No Project Roles",
req: &pbs.ListRolesRequest{ScopeId: pNoRoles.GetPublicId()},
res: &pbs.ListRolesResponse{},
res: &pbs.ListRolesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List proj roles recursively",
req: &pbs.ListRolesRequest{ScopeId: pWithRoles.GetPublicId(), Recursive: true},
res: &pbs.ListRolesResponse{
Items: wantProjRoles,
Items: wantProjRoles,
EstItemCount: uint32(len(wantProjRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List org roles recursively",
req: &pbs.ListRolesRequest{ScopeId: oNoRoles.GetPublicId(), Recursive: true},
res: &pbs.ListRolesResponse{
Items: wantProjRoles,
Items: wantProjRoles,
EstItemCount: uint32(len(wantProjRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List global roles recursively",
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true},
res: &pbs.ListRolesResponse{
Items: totalRoles,
Items: totalRoles,
EstItemCount: uint32(len(totalRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter to org roles",
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithRoles.GetPublicId())},
res: &pbs.ListRolesResponse{Items: wantOrgRoles},
res: &pbs.ListRolesResponse{
Items: wantOrgRoles,
EstItemCount: uint32(len(wantOrgRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter to proj roles",
req: &pbs.ListRolesRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, pWithRoles.GetPublicId())},
res: &pbs.ListRolesResponse{Items: wantProjRoles},
res: &pbs.ListRolesResponse{
Items: wantProjRoles,
EstItemCount: uint32(len(wantProjRoles)),
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter to no roles",
req: &pbs.ListRolesRequest{ScopeId: pWithRoles.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
res: &pbs.ListRolesResponse{},
res: &pbs.ListRolesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter Bad Format",
@@ -308,7 +384,7 @@ func TestList(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(err, "Couldn't create new role service.")
// Test the non-anon case
@@ -323,6 +399,7 @@ func TestList(t *testing.T) {
got,
tc.res,
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
@@ -345,10 +422,351 @@ func TestList(t *testing.T) {
}
}
func roleToProto(r *iam.Role, scope *scopes.ScopeInfo, authorizedActions []string) *pb.Role {
return &pb.Role{
Id: r.GetPublicId(),
ScopeId: r.GetScopeId(),
Scope: scope,
CreatedTime: r.GetCreateTime().GetTimestamp(),
UpdatedTime: r.GetUpdateTime().GetTimestamp(),
GrantScopeId: &wrapperspb.StringValue{Value: r.GetGrantScopeId()},
Version: r.GetVersion(),
AuthorizedActions: testAuthorizedActions,
}
}
func TestListPagination(t *testing.T) {
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
globals.RefreshReadLookbackDuration = 0
t.Cleanup(func() {
globals.RefreshReadLookbackDuration = oldReadTimeout
})
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
sqlDB, err := conn.SqlDB(ctx)
require.NoError(t, err)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(ctx, rw, rw, kms)
}
tokenRepo, err := tokenRepoFn()
require.NoError(t, err)
// Run analyze to update postgres meta tables
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
oNoRoles, pWithRoles := iam.TestScopes(t, iamRepo, iam.WithSkipDefaultRoleCreation(true))
oWithRoles, pNoRoles := iam.TestScopes(t, iamRepo, iam.WithSkipDefaultRoleCreation(true))
authMethod := password.TestAuthMethods(t, conn, "global", 1)[0]
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
u := iam.TestUser(t, iamRepo, "global", iam.WithAccountIds(acct.PublicId))
var allRoles []*pb.Role
for _, scope := range []*iam.Scope{oWithRoles, oNoRoles, pWithRoles, pNoRoles} {
allowedRole := iam.TestRole(t, conn, scope.GetPublicId())
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "id=*;type=*;actions=*")
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
allRoles = append(allRoles, roleToProto(allowedRole,
&scopes.ScopeInfo{
Id: scope.GetPublicId(),
ParentScopeId: scope.GetParentId(),
Type: scope.GetType(),
},
testAuthorizedActions,
))
}
at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
require.NoError(t, err)
// Test without anon user
requestInfo := authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
Token: at.GetToken(),
}
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
var safeToDeleteRole string
orgScopeInfo := &scopes.ScopeInfo{Id: oWithRoles.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}
projScopeInfo := &scopes.ScopeInfo{Id: pWithRoles.GetPublicId(), Type: scope.Project.String(), ParentScopeId: pWithRoles.GetParentId()}
for i := 0; i < 10; i++ {
or := iam.TestRole(t, conn, oWithRoles.GetPublicId())
allRoles = append(allRoles, roleToProto(or, orgScopeInfo, testAuthorizedActions))
pr := iam.TestRole(t, conn, pWithRoles.GetPublicId())
allRoles = append(allRoles, roleToProto(pr, projScopeInfo, testAuthorizedActions))
safeToDeleteRole = pr.GetPublicId()
}
slices.Reverse(allRoles)
a, err := roles.NewService(ctx, iamRepoFn, 1000)
require.NoError(t, err, "Couldn't create new user service.")
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(context.Background(), "analyze")
require.NoError(t, err)
itemCount := uint32(len(allRoles))
testPageSize := int((itemCount - 2) / 2)
// Start paginating, recursively
req := &pbs.ListRolesRequest{
ScopeId: "global",
Recursive: true,
Filter: "",
ListToken: "",
PageSize: uint32(testPageSize),
}
got, err := a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), testPageSize)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: allRoles[0:testPageSize],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
// In addition to the added roles, there are the roles added
// by the test setup when specifying the permissions of the
// requester
EstItemCount: itemCount,
},
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
// Request second page
req.ListToken = got.ListToken
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), testPageSize)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: allRoles[testPageSize : testPageSize*2],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
// Request rest of results
req.ListToken = got.ListToken
req.PageSize = 10
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 2)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: allRoles[testPageSize*2:],
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
// Update 2 roles and see them in the refresh
r1 := allRoles[len(allRoles)-1]
r1.Description = wrapperspb.String("updated1")
resp1, err := a.UpdateRole(ctx, &pbs.UpdateRoleRequest{
Id: r1.GetId(),
Item: &pb.Role{Description: r1.GetDescription(), Version: r1.GetVersion()},
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
})
require.NoError(t, err)
r1.UpdatedTime = resp1.GetItem().GetUpdatedTime()
r1.Version = resp1.GetItem().GetVersion()
allRoles = append([]*pb.Role{r1}, allRoles[:len(allRoles)-1]...)
r2 := allRoles[len(allRoles)-1]
r2.Description = wrapperspb.String("updated2")
resp2, err := a.UpdateRole(ctx, &pbs.UpdateRoleRequest{
Id: r2.GetId(),
Item: &pb.Role{Description: r2.GetDescription(), Version: r2.GetVersion()},
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
})
require.NoError(t, err)
r2.UpdatedTime = resp2.GetItem().GetUpdatedTime()
r2.Version = resp2.GetItem().GetVersion()
allRoles = append([]*pb.Role{r2}, allRoles[:len(allRoles)-1]...)
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
// Request updated results
req.ListToken = got.ListToken
req.PageSize = 1
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: []*pb.Role{allRoles[0]},
ResponseType: "delta",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
// Get next page
req.ListToken = got.ListToken
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: []*pb.Role{allRoles[1]},
ResponseType: "complete",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
// Request new page with filter requiring looping
// to fill the page.
req.ListToken = ""
req.PageSize = 1
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, allRoles[len(allRoles)-2].Id, allRoles[len(allRoles)-1].Id)
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: []*pb.Role{allRoles[len(allRoles)-2]},
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
// Should be empty again
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
req.ListToken = got.ListToken
// Get the second page
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: []*pb.Role{allRoles[len(allRoles)-1]},
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
_, err = iamRepo.DeleteRole(ctx, safeToDeleteRole)
require.NoError(t, err)
req.ListToken = got.ListToken
got, err = a.ListRoles(ctx, req)
require.NoError(t, err)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListRolesResponse{
Items: nil,
ResponseType: "complete",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: []string{safeToDeleteRole},
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListRolesResponse{}, "list_token"),
),
)
}
func TestDelete(t *testing.T) {
or, pr, repoFn := createDefaultRolesAndRepo(t)
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(t, err, "Error when getting new role service.")
cases := []struct {
@@ -414,7 +832,7 @@ func TestDelete_twice(t *testing.T) {
assert, require := assert.New(t), require.New(t)
or, pr, repoFn := createDefaultRolesAndRepo(t)
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(err, "Error when getting new role service")
req := &pbs.DeleteRoleRequest{
Id: or.GetPublicId(),
@@ -559,7 +977,7 @@ func TestCreate(t *testing.T) {
req := proto.Clone(toMerge).(*pbs.CreateRoleRequest)
proto.Merge(req, tc.req)
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(err, "Error when getting new role service.")
got, gErr := s.CreateRole(auth.DisabledAuthTestContext(repoFn, tc.req.GetItem().GetScopeId()), req)
@@ -636,7 +1054,7 @@ func TestUpdate(t *testing.T) {
var orVersion uint32 = 1
var prVersion uint32 = 1
tested, err := roles.NewService(ctx, repoFn)
tested, err := roles.NewService(ctx, repoFn, 0)
require.NoError(t, err, "Error when getting new role service.")
resetRoles := func(proj bool) {
@@ -1052,7 +1470,7 @@ func TestAddPrincipal(t *testing.T) {
return iamRepo, nil
}
o, p := iam.TestScopes(t, iamRepo)
s, err := roles.NewService(ctx, repoFn)
s, err := roles.NewService(ctx, repoFn, 0)
require.NoError(t, err, "Error when getting new role service.")
kmsCache := kms.TestKms(t, conn, wrap)
@@ -1257,7 +1675,7 @@ func TestSetPrincipal(t *testing.T) {
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(t, err, "Error when getting new role service.")
o, p := iam.TestScopes(t, iamRepo)
@@ -1459,7 +1877,7 @@ func TestRemovePrincipal(t *testing.T) {
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := roles.NewService(ctx, repoFn)
s, err := roles.NewService(ctx, repoFn, 0)
require.NoError(t, err, "Error when getting new role service.")
o, p := iam.TestScopes(t, iamRepo)
@@ -1721,7 +2139,7 @@ func TestAddGrants(t *testing.T) {
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(t, err, "Error when getting new role service.")
addCases := []struct {
@@ -1869,7 +2287,7 @@ func TestSetGrants(t *testing.T) {
return iamRepo, nil
}
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(t, err, "Error when getting new role service.")
setCases := []struct {
@@ -2014,7 +2432,7 @@ func TestRemoveGrants(t *testing.T) {
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := roles.NewService(context.Background(), repoFn)
s, err := roles.NewService(context.Background(), repoFn, 1000)
require.NoError(t, err, "Error when getting new role service.")
removeCases := []struct {
@@ -967,15 +967,18 @@ func TestCreate(t *testing.T) {
if withUserId {
repo, err := repoFn()
require.NoError(err)
roles, err := repo.ListRoles(ctx, []string{got.GetItem().GetId()})
noopFilter := func(ctx context.Context, item *iam.Role) (bool, error) {
return true, nil
}
roles, err := iam.ListRoles(ctx, []byte("test"), globals.DefaultMaxPageSize, noopFilter, repo, []string{got.GetItem().GetId()})
require.NoError(err)
switch tc.scopeId {
case defaultOrg.PublicId:
require.Len(roles, 2)
require.Len(roles.Items, 2)
case "global":
require.Len(roles, 2)
require.Len(roles.Items, 2)
}
for _, role := range roles {
for _, role := range roles.Items {
switch role.GetName() {
case "Administration":
assert.Equal(fmt.Sprintf("Role created for administration of scope %s by user %s at its creation time", got.GetItem().GetId(), userId), role.GetDescription())
@@ -0,0 +1,14 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
-- Add new indexes for the create time and update time queries.
create index iam_role_create_time_public_id_idx
on iam_role (create_time desc, public_id asc);
create index iam_role_update_time_public_id_idx
on iam_role (update_time desc, public_id asc);
analyze iam_role;
commit;
@@ -0,0 +1,12 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(2);
select has_index('iam_role', 'iam_role_create_time_public_id_idx', array['create_time', 'public_id']);
select has_index('iam_role', 'iam_role_update_time_public_id_idx', array['update_time', 'public_id']);
select * from finish();
rollback;
+43
View File
@@ -2171,6 +2171,21 @@
"in": "query",
"required": false,
"type": "string"
},
{
"name": "list_token",
"description": "An opaque token used to continue an existing iteration or\nrequest updated items. If not specified, pagination\nwill start from the beginning.",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "page_size",
"description": "The maximum size of a page in this iteration.\nIf unset, the default page size configured will be used.\nIf the page_size is greater than the default page configured,\nan error will be returned.",
"in": "query",
"required": false,
"type": "integer",
"format": "int64"
}
],
"tags": [
@@ -7828,6 +7843,34 @@
"type": "object",
"$ref": "#/definitions/controller.api.resources.roles.v1.Role"
}
},
"response_type": {
"type": "string",
"description": "The type of response, either \"delta\" or \"complete\".\nDelta signifies that this is part of a paginated result\nor an update to a previously completed pagination.\nComplete signifies that it is the last page."
},
"list_token": {
"type": "string",
"description": "An opaque token used to continue an existing pagination or\nrequest updated items. Use this token in the next list request\nto request the next page."
},
"sort_by": {
"type": "string",
"description": "The name of the field which the items are sorted by."
},
"sort_dir": {
"type": "string",
"description": "The direction of the sort, either \"asc\" or \"desc\"."
},
"removed_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of item IDs that have been removed since they were returned\nas part of a pagination. They should be dropped from any client cache.\nThis may contain items that are not known to the cache, if they were\ncreated and deleted between listings."
},
"est_item_count": {
"type": "integer",
"format": "int64",
"description": "An estimate at the total items available. This may change during pagination."
}
}
},
@@ -129,6 +129,15 @@ type ListRolesRequest struct {
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,proto3" json:"scope_id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"`
Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"`
Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"public"` // @gotags: `class:"public"`
// An opaque token used to continue an existing iteration or
// request updated items. If not specified, pagination
// will start from the beginning.
ListToken string `protobuf:"bytes,40,opt,name=list_token,proto3" json:"list_token,omitempty" class:"public"` // @gotags: `class:"public"`
// The maximum size of a page in this iteration.
// If unset, the default page size configured will be used.
// If the page_size is greater than the default page configured,
// an error will be returned.
PageSize uint32 `protobuf:"varint,50,opt,name=page_size,proto3" json:"page_size,omitempty" class:"public"` // @gotags: `class:"public"`
}
func (x *ListRolesRequest) Reset() {
@@ -184,12 +193,46 @@ func (x *ListRolesRequest) GetFilter() string {
return ""
}
func (x *ListRolesRequest) GetListToken() string {
if x != nil {
return x.ListToken
}
return ""
}
func (x *ListRolesRequest) GetPageSize() uint32 {
if x != nil {
return x.PageSize
}
return 0
}
type ListRolesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Items []*roles.Role `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
// The type of response, either "delta" or "complete".
// Delta signifies that this is part of a paginated result
// or an update to a previously completed pagination.
// Complete signifies that it is the last page.
ResponseType string `protobuf:"bytes,2,opt,name=response_type,proto3" json:"response_type,omitempty" class:"public"` // @gotags: `class:"public"`
// An opaque token used to continue an existing pagination or
// request updated items. Use this token in the next list request
// to request the next page.
ListToken string `protobuf:"bytes,3,opt,name=list_token,proto3" json:"list_token,omitempty" class:"public"` // @gotags: `class:"public"`
// The name of the field which the items are sorted by.
SortBy string `protobuf:"bytes,4,opt,name=sort_by,proto3" json:"sort_by,omitempty" class:"public"` // @gotags: `class:"public"`
// The direction of the sort, either "asc" or "desc".
SortDir string `protobuf:"bytes,5,opt,name=sort_dir,proto3" json:"sort_dir,omitempty" class:"public"` // @gotags: `class:"public"`
// A list of item IDs that have been removed since they were returned
// as part of a pagination. They should be dropped from any client cache.
// This may contain items that are not known to the cache, if they were
// created and deleted between listings.
RemovedIds []string `protobuf:"bytes,6,rep,name=removed_ids,proto3" json:"removed_ids,omitempty" class:"public"` // @gotags: `class:"public"`
// An estimate at the total items available. This may change during pagination.
EstItemCount uint32 `protobuf:"varint,7,opt,name=est_item_count,proto3" json:"est_item_count,omitempty" class:"public"` // @gotags: `class:"public"`
}
func (x *ListRolesResponse) Reset() {
@@ -231,6 +274,48 @@ func (x *ListRolesResponse) GetItems() []*roles.Role {
return nil
}
func (x *ListRolesResponse) GetResponseType() string {
if x != nil {
return x.ResponseType
}
return ""
}
func (x *ListRolesResponse) GetListToken() string {
if x != nil {
return x.ListToken
}
return ""
}
func (x *ListRolesResponse) GetSortBy() string {
if x != nil {
return x.SortBy
}
return ""
}
func (x *ListRolesResponse) GetSortDir() string {
if x != nil {
return x.SortDir
}
return ""
}
func (x *ListRolesResponse) GetRemovedIds() []string {
if x != nil {
return x.RemovedIds
}
return nil
}
func (x *ListRolesResponse) GetEstItemCount() uint32 {
if x != nil {
return x.EstItemCount
}
return 0
}
type CreateRoleRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1224,266 +1309,282 @@ var file_controller_api_services_v1_role_service_proto_rawDesc = []byte{
0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x64, 0x0a, 0x10, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72,
0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c,
0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65,
0x72, 0x22, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65,
0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa2, 0x01, 0x0a, 0x10,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09,
0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69,
0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
0x32, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65,
0x22, 0x98, 0x02, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x50, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52,
0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c,
0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a,
0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12,
0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x9e, 0x01, 0x0a,
0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12,
0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b,
0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a,
0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c,
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52,
0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6a, 0x0a, 0x18, 0x41,
0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69,
0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x58, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x52, 0x6f,
0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f,
0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x22, 0x6a, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6c,
0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73,
0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f,
0x72, 0x74, 0x5f, 0x62, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69,
0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69,
0x72, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73,
0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f,
0x69, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x73, 0x74,
0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x11, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a,
0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72,
0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f,
0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,
0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d,
0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65,
0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x6a, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e,
0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a,
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63,
0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d,
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x58, 0x0a,
0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61,
0x19, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61,
0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c,
0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6d, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70,
0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x5b, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,
0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69,
0x74, 0x65, 0x6d, 0x22, 0x66, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73,
0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x54, 0x0a, 0x15, 0x41,
0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f,
0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x22, 0x66, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e,
0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x54, 0x0a, 0x15, 0x53, 0x65, 0x74,
0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22,
0x69, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61,
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61,
0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x57, 0x0a, 0x18, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,
0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69,
0x74, 0x65, 0x6d, 0x32, 0x98, 0x11, 0x0a, 0x0b, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12,
0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x92, 0x41, 0x15, 0x12, 0x13, 0x47,
0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c,
0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e,
0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90,
0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f,
0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x92, 0x41, 0x12, 0x12, 0x10,
0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x2e,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65,
0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20,
0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09,
0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32,
0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12,
0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d,
0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6a, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x52, 0x6f,
0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a,
0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03,
0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f,
0x69, 0x64, 0x73, 0x22, 0x58, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6d, 0x0a,
0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63,
0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x5b, 0x0a, 0x1c,
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x66, 0x0a, 0x14, 0x41, 0x64, 0x64,
0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67,
0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03,
0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x73, 0x22, 0x54, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c,
0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x66, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x52, 0x6f,
0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x61,
0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22,
0x54, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x69, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52,
0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x09, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73,
0x22, 0x57, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x6f, 0x6c, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x98, 0x11, 0x0a, 0x0b, 0x52, 0x6f,
0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65,
0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34,
0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67,
0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f,
0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c,
0x65, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20,
0x52, 0x6f, 0x6c, 0x65, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76,
0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6c,
0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12,
0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92,
0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f,
0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72,
0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd8, 0x01, 0x0a, 0x11, 0x41, 0x64,
0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12,
0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64,
0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92,
0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x52, 0x6f,
0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x52, 0x6f, 0x6c, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x73, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a,
0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12,
0xd8, 0x01, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63,
0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x92, 0x41,
0x25, 0x12, 0x23, 0x41, 0x64, 0x64, 0x73, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e,
0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61,
0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x73, 0x12, 0x97, 0x02, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65,
0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65,
0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x56, 0x92, 0x41, 0x25, 0x12, 0x23, 0x41, 0x64, 0x64, 0x73, 0x20, 0x55, 0x73,
0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31,
0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d,
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x97, 0x02, 0x0a, 0x11, 0x53,
0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73,
0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x01, 0x92, 0x41, 0x63, 0x12, 0x61, 0x53,
0x65, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65,
0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61,
0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64,
0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22,
0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a,
0x73, 0x65, 0x74, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xf7,
0x01, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69,
0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50,
0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61,
0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6c, 0x92, 0x41, 0x38, 0x12,
0x36, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65,
0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64,
0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a,
0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x70, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xba, 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64,
0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47,
0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c,
0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x44, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x64, 0x64, 0x73, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74,
0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63,
0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x01,
0x92, 0x41, 0x63, 0x12, 0x61, 0x53, 0x65, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61,
0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20,
0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67,
0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x20,
0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65,
0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69,
0x70, 0x61, 0x6c, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52,
0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0x37, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x72,
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x6c, 0x92, 0x41, 0x38, 0x12, 0x36, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74,
0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65,
0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,
0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x2b, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x20, 0x2f, 0x76,
0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d,
0x6f, 0x76, 0x65, 0x2d, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x12, 0xba,
0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73,
0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64,
0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e,
0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x64, 0x64, 0x73,
0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c,
0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d,
0x3a, 0x61, 0x64, 0x64, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x0d,
0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f,
0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74,
0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x80, 0x01, 0x92, 0x41, 0x53, 0x12, 0x51, 0x53, 0x65, 0x74, 0x20, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c,
0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f,
0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74,
0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f,
0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x67,
0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xf7, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c,
0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x92,
0x41, 0x53, 0x12, 0x51, 0x53, 0x65, 0x74, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66,
0x6f, 0x72, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76,
0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74,
0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63,
0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f,
0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12,
0xcc, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c,
0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x4d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65,
0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d,
0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x42, 0x4d,
0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x72, 0x6f, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x67,
0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x6f,
0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d,
0x6f, 0x76, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x52, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x73, 0x20, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
0x61, 0x20, 0x52, 0x6f, 0x6c, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a,
0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6c, 0x65,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x67, 0x72,
0x61, 0x6e, 0x74, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67,
0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
+14 -1
View File
@@ -3,7 +3,11 @@
package iam
import "io"
import (
"io"
"github.com/hashicorp/boundary/internal/pagination"
)
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
@@ -32,6 +36,7 @@ type options struct {
withRandomReader io.Reader
withAccountIds []string
withPrimaryAuthMethodId string
withStartPageAfterItem pagination.Item
}
func getDefaultOptions() options {
@@ -144,3 +149,11 @@ func WithPrimaryAuthMethodId(id string) Option {
o.withPrimaryAuthMethodId = id
}
}
// WithStartPageAfterItem is used to paginate over the results.
// The next page will start after the provided item.
func WithStartPageAfterItem(item pagination.Item) Option {
return func(o *options) {
o.withStartPageAfterItem = item
}
}
+4
View File
@@ -176,4 +176,8 @@ const (
)
select role_id as role_id, role_scope as scope_id, role_grant as grant from final;
`
estimateCountRoles = `
select reltuples::bigint as estimate from pg_class where oid in ('iam_role'::regclass)
`
)
+133 -9
View File
@@ -5,10 +5,13 @@ package iam
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/go-dbw"
)
@@ -184,16 +187,137 @@ func (r *Repository) DeleteRole(ctx context.Context, withPublicId string, _ ...O
return rowsDeleted, nil
}
// ListRoles lists roles in the given scopes and supports WithLimit option.
func (r *Repository) ListRoles(ctx context.Context, withScopeIds []string, opt ...Option) ([]*Role, error) {
const op = "iam.(Repository).ListRoles"
// listRoles lists roles in the given scopes and supports WithLimit option.
func (r *Repository) listRoles(ctx context.Context, withScopeIds []string, opt ...Option) ([]*Role, time.Time, error) {
const op = "iam.(Repository).listRoles"
if len(withScopeIds) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
var roles []*Role
err := r.list(ctx, &roles, "scope_id in (?)", []any{withScopeIds}, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
opts := getOpts(opt...)
limit := r.defaultLimit
switch {
case opts.withLimit > 0:
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
case opts.withLimit < 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
}
return roles, nil
var args []any
whereClause := "scope_id in @scope_ids"
args = append(args, sql.Named("scope_ids", withScopeIds))
if opts.withStartPageAfterItem != nil {
whereClause = fmt.Sprintf("(create_time, public_id) < (@last_item_create_time, @last_item_id) and %s", whereClause)
args = append(args,
sql.Named("last_item_create_time", opts.withStartPageAfterItem.GetCreateTime()),
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
)
}
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("create_time desc, public_id asc")}
return r.queryRoles(ctx, whereClause, args, dbOpts...)
}
// listRolesRefresh lists roles in the given scopes and supports the
// WithLimit and WithStartPageAfterItem options.
func (r *Repository) listRolesRefresh(ctx context.Context, updatedAfter time.Time, withScopeIds []string, opt ...Option) ([]*Role, time.Time, error) {
const op = "iam.(Repository).listRolesRefresh"
switch {
case updatedAfter.IsZero():
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
case len(withScopeIds) == 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
opts := getOpts(opt...)
limit := r.defaultLimit
switch {
case opts.withLimit > 0:
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
case opts.withLimit < 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
}
var args []any
whereClause := "update_time > @updated_after_time and scope_id in @scope_ids"
args = append(args,
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
sql.Named("scope_ids", withScopeIds),
)
if opts.withStartPageAfterItem != nil {
whereClause = fmt.Sprintf("(update_time, public_id) < (@last_item_update_time, @last_item_id) and %s", whereClause)
args = append(args,
sql.Named("last_item_update_time", opts.withStartPageAfterItem.GetUpdateTime()),
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
)
}
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("update_time desc, public_id asc")}
return r.queryRoles(ctx, whereClause, args, dbOpts...)
}
func (r *Repository) queryRoles(ctx context.Context, whereClause string, args []any, opt ...db.Option) ([]*Role, time.Time, error) {
const op = "iam.(Repository).queryRoles"
var transactionTimestamp time.Time
var ret []*Role
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rd db.Reader, w db.Writer) error {
var inRet []*Role
if err := rd.SearchWhere(ctx, &inRet, whereClause, args, opt...); err != nil {
return errors.Wrap(ctx, err, op)
}
ret = inRet
var err error
transactionTimestamp, err = rd.Now(ctx)
return err
}); err != nil {
return nil, time.Time{}, err
}
return ret, transactionTimestamp, nil
}
// listRoleDeletedIds lists the public IDs of any roles deleted since the timestamp provided.
func (r *Repository) listRoleDeletedIds(ctx context.Context, since time.Time) ([]string, time.Time, error) {
const op = "iam.(Repository).listRoleDeletedIds"
var deletedResources []*deletedRole
var transactionTimestamp time.Time
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, _ db.Writer) error {
if err := r.SearchWhere(ctx, &deletedResources, "delete_time >= ?", []any{since}); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted roles"))
}
var err error
transactionTimestamp, err = r.Now(ctx)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to get transaction timestamp"))
}
return nil
}); err != nil {
return nil, time.Time{}, err
}
var dIds []string
for _, res := range deletedResources {
dIds = append(dIds, res.PublicId)
}
return dIds, transactionTimestamp, nil
}
// estimatedRoleCount returns an estimate of the total number of items in the iam_role table.
func (r *Repository) estimatedRoleCount(ctx context.Context) (int, error) {
const op = "iam.(Repository).estimatedRoleCount"
rows, err := r.reader.Query(ctx, estimateCountRoles, nil)
if err != nil {
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total roles"))
}
var count int
for rows.Next() {
if err := r.reader.ScanRows(ctx, rows, &count); err != nil {
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total roles"))
}
}
return count, nil
}
+191 -10
View File
@@ -13,6 +13,7 @@ import (
dbassert "github.com/hashicorp/boundary/internal/db/assert"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/go-uuid"
"github.com/stretchr/testify/assert"
@@ -553,13 +554,15 @@ func TestRepository_DeleteRole(t *testing.T) {
}
}
func TestRepository_ListRoles(t *testing.T) {
func TestRepository_listRoles(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
const testLimit = 10
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo := TestRepo(t, conn, wrapper, WithLimit(testLimit))
org, proj := TestScopes(t, repo)
org, proj := TestScopes(t, repo, WithSkipDefaultRoleCreation(true))
type args struct {
withScopeId string
@@ -574,7 +577,7 @@ func TestRepository_ListRoles(t *testing.T) {
wantErr bool
}{
{
name: "no-limit",
name: "negative-limit",
createCnt: repo.defaultLimit + 1,
createScopeId: org.PublicId,
args: args{
@@ -582,10 +585,10 @@ func TestRepository_ListRoles(t *testing.T) {
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 1,
wantErr: false,
wantErr: true,
},
{
name: "no-limit-proj-role",
name: "negative-limit-proj-role",
createCnt: repo.defaultLimit + 1,
createScopeId: proj.PublicId,
args: args{
@@ -593,7 +596,7 @@ func TestRepository_ListRoles(t *testing.T) {
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 1,
wantErr: false,
wantErr: true,
},
{
name: "default-limit",
@@ -630,21 +633,114 @@ func TestRepository_ListRoles(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db.TestDeleteWhere(t, conn, func() any { r := allocRole(); return &r }(), "1=1")
t.Cleanup(func() {
db.TestDeleteWhere(t, conn, func() any { r := allocRole(); return &r }(), "1=1")
})
testRoles := []*Role{}
for i := 0; i < tt.createCnt; i++ {
testRoles = append(testRoles, TestRole(t, conn, tt.createScopeId))
}
assert.Equal(tt.createCnt, len(testRoles))
got, err := repo.ListRoles(context.Background(), []string{tt.args.withScopeId}, tt.args.opt...)
got, ttime, err := repo.listRoles(context.Background(), []string{tt.args.withScopeId}, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
// Transaction timestamp should be within ~10 seconds of now
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
})
}
t.Run("withStartPageAfter", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
for i := 0; i < 10; i++ {
_ = TestRole(t, conn, org.GetPublicId())
}
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
page1, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2))
require.NoError(err)
require.Len(page1, 2)
// Transaction timestamp should be within ~10 seconds of now
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
page2, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page1[1]))
require.NoError(err)
require.Len(page2, 2)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
for _, item := range page1 {
assert.NotEqual(item.GetPublicId(), page2[0].GetPublicId())
assert.NotEqual(item.GetPublicId(), page2[1].GetPublicId())
}
page3, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page2[1]))
require.NoError(err)
require.Len(page3, 2)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
for _, item := range page2 {
assert.NotEqual(item.GetPublicId(), page3[0].GetPublicId())
assert.NotEqual(item.GetPublicId(), page3[1].GetPublicId())
}
page4, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page3[1]))
require.NoError(err)
assert.Len(page4, 2)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
for _, item := range page3 {
assert.NotEqual(item.GetPublicId(), page4[0].GetPublicId())
assert.NotEqual(item.GetPublicId(), page4[1].GetPublicId())
}
page5, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page4[1]))
require.NoError(err)
assert.Len(page5, 2)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
for _, item := range page4 {
assert.NotEqual(item.GetPublicId(), page5[0].GetPublicId())
assert.NotEqual(item.GetPublicId(), page5[1].GetPublicId())
}
page6, ttime, err := repo.listRoles(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page5[1]))
require.NoError(err)
assert.Empty(page6)
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
// Create 2 new roles
newR1 := TestRole(t, conn, org.GetPublicId())
newR2 := TestRole(t, conn, org.GetPublicId())
// since it will return newest to oldest, we get page1[1] first
page7, ttime, err := repo.listRolesRefresh(
ctx,
time.Now().Add(-1*time.Second),
[]string{org.GetPublicId()},
WithLimit(1),
)
require.NoError(err)
require.Len(page7, 1)
require.Equal(page7[0].GetPublicId(), newR2.GetPublicId())
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
page8, ttime, err := repo.listRolesRefresh(
context.Background(),
time.Now().Add(-1*time.Second),
[]string{org.GetPublicId()},
WithLimit(1),
WithStartPageAfterItem(page7[0]),
)
require.NoError(err)
require.Len(page8, 1)
require.Equal(page8[0].GetPublicId(), newR1.GetPublicId())
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
})
}
func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) {
@@ -667,7 +763,92 @@ func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) {
total++
}
got, err := repo.ListRoles(context.Background(), []string{"global", org.GetPublicId(), proj.GetPublicId()})
got, ttime, err := repo.listRoles(context.Background(), []string{"global", org.GetPublicId(), proj.GetPublicId()})
require.NoError(t, err)
assert.Equal(t, total, len(got))
assert.Equal(t, total, len(got)) // Transaction timestamp should be within ~10 seconds of now
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
}
func Test_listRoleDeletedIds(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo, WithSkipDefaultRoleCreation(true))
r := TestRole(t, conn, org.GetPublicId())
// Expect no entries at the start
deletedIds, ttime, err := repo.listRoleDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
require.NoError(t, err)
require.Empty(t, deletedIds)
// Transaction timestamp should be within ~10 seconds of now
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
// Delete a role
_, err = repo.DeleteRole(ctx, r.GetPublicId())
require.NoError(t, err)
// Expect a single entry
deletedIds, ttime, err = repo.listRoleDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
require.NoError(t, err)
require.Equal(t, []string{r.GetPublicId()}, deletedIds)
// Transaction timestamp should be within ~10 seconds of now
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
// Try again with the time set to now, expect no entries
deletedIds, ttime, err = repo.listRoleDeletedIds(ctx, time.Now())
require.NoError(t, err)
require.Empty(t, deletedIds)
// Transaction timestamp should be within ~10 seconds of now
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
}
func Test_estimatedRoleCount(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
sqlDb, err := conn.SqlDB(ctx)
require.NoError(t, err)
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(t, err)
// Run analyze to update estimate
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(t, err)
// Check total entries at start, expect 0
numItems, err := repo.estimatedRoleCount(ctx)
require.NoError(t, err)
assert.Equal(t, 0, numItems)
iamRepo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, iamRepo, WithSkipDefaultRoleCreation(true))
// Create a role, expect 1 entry
r := TestRole(t, conn, org.GetPublicId())
// Run analyze to update estimate
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(t, err)
numItems, err = repo.estimatedRoleCount(ctx)
require.NoError(t, err)
assert.Equal(t, 1, numItems)
// Delete the role, expect 0 again
_, err = repo.DeleteRole(ctx, r.GetPublicId())
require.NoError(t, err)
// Run analyze to update estimate
_, err = sqlDb.ExecContext(ctx, "analyze")
require.NoError(t, err)
numItems, err = repo.estimatedRoleCount(ctx)
require.NoError(t, err)
assert.Equal(t, 0, numItems)
}
+1 -1
View File
@@ -124,7 +124,7 @@ func Test_Repository_Scope_Create(t *testing.T) {
require.NoError(err)
assert.True(proto.Equal(foundScope, s))
foundRoles, err := repo.ListRoles(context.Background(), []string{foundScope.GetPublicId()})
foundRoles, _, err := repo.listRoles(context.Background(), []string{foundScope.GetPublicId()})
require.NoError(err)
numFound := 2
if skipCreate {
+11
View File
@@ -7,6 +7,7 @@ import (
"context"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/types/action"
@@ -115,3 +116,13 @@ func (r *Role) TableName() string {
func (r *Role) SetTableName(n string) {
r.tableName = n
}
type deletedRole struct {
PublicId string `gorm:"primary_key"`
DeleteTime *timestamp.Timestamp
}
// TableName returns the tablename to override the default gorm table name
func (s *deletedRole) TableName() string {
return "iam_role_deleted"
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"time"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/pagination"
)
// ListRoles lists up to page size roles, filtering out entries that
// do not pass the filter item function. It will automatically request
// more roles from the database, at page size chunks, to fill the page.
// It returns a new list token used to continue pagination or refresh items.
// Roles are ordered by create time descending (most recently created first).
func ListRoles(
ctx context.Context,
grantsHash []byte,
pageSize int,
filterItemFn pagination.ListFilterFunc[*Role],
repo *Repository,
withScopeIds []string,
) (*pagination.ListResponse[*Role], error) {
const op = "iam.ListRoles"
switch {
case len(grantsHash) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
case pageSize < 1:
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
case filterItemFn == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
case repo == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
case withScopeIds == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
}
listItemsFn := func(ctx context.Context, lastPageItem *Role, limit int) ([]*Role, time.Time, error) {
opts := []Option{
WithLimit(limit),
}
if lastPageItem != nil {
opts = append(opts, WithStartPageAfterItem(lastPageItem))
}
return repo.listRoles(ctx, withScopeIds, opts...)
}
return pagination.List(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount)
}
+583
View File
@@ -0,0 +1,583 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam_test
import (
"context"
"slices"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestService_ListRoles(t *testing.T) {
fiveDaysAgo := time.Now()
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
globals.RefreshReadLookbackDuration = 0
t.Cleanup(func() {
globals.RefreshReadLookbackDuration = oldReadTimeout
})
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
sqlDB, err := conn.SqlDB(context.Background())
require.NoError(t, err)
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
org, _ := iam.TestScopes(t, iamRepo, iam.WithSkipDefaultRoleCreation(true))
var allResources []*iam.Role
for i := 0; i < 5; i++ {
r := iam.TestRole(t, conn, org.GetPublicId())
allResources = append(allResources, r)
}
repo, err := iam.NewRepository(ctx, rw, rw, kms)
require.NoError(t, err)
// Reverse since we read items in descending order (newest first)
slices.Reverse(allResources)
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(iam.Role{}, store.Role{}, timestamp.Timestamp{}, timestamppb.Timestamp{})
t.Run("List validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err := iam.ListRoles(ctx, nil, 1, filterFunc, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err := iam.ListRoles(ctx, []byte("some hash"), 0, filterFunc, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err := iam.ListRoles(ctx, []byte("some hash"), -1, filterFunc, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter func", func(t *testing.T) {
t.Parallel()
_, err := iam.ListRoles(ctx, []byte("some hash"), 1, nil, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err := iam.ListRoles(ctx, []byte("some hash"), 1, filterFunc, nil, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing scope ids", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err := iam.ListRoles(ctx, []byte("some hash"), 1, filterFunc, repo, nil)
require.ErrorContains(t, err, "missing scope ids")
})
})
t.Run("ListPage validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter func", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "token did not have a pagination token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing scope ids", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
require.ErrorContains(t, err, "missing scope ids")
})
})
t.Run("ListRefresh validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter func", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "token did not have a start-refresh token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing scope ids", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
require.ErrorContains(t, err, "missing scope ids")
})
})
t.Run("ListRefreshPage validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil filter func", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing filter item callback")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
require.ErrorContains(t, err, "token did not have a refresh token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing scope ids", func(t *testing.T) {
t.Parallel()
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Role, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
require.ErrorContains(t, err, "missing scope ids")
})
})
t.Run("simple pagination", func(t *testing.T) {
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
resp, err := iam.ListRoles(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 5)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], allResources[0], cmpIgnoreUnexportedOpts))
resp2, err := iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 5)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 1)
require.Empty(t, cmp.Diff(resp2.Items[0], allResources[1], cmpIgnoreUnexportedOpts))
resp3, err := iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 5)
require.Empty(t, resp3.DeletedIds)
require.Len(t, resp3.Items, 1)
require.Empty(t, cmp.Diff(resp3.Items[0], allResources[2], cmpIgnoreUnexportedOpts))
resp4, err := iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp4.CompleteListing)
require.Equal(t, resp4.EstimatedItemCount, 5)
require.Empty(t, resp4.DeletedIds)
require.Len(t, resp4.Items, 1)
require.Empty(t, cmp.Diff(resp4.Items[0], allResources[3], cmpIgnoreUnexportedOpts))
resp5, err := iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 5)
require.Empty(t, resp5.DeletedIds)
require.Len(t, resp5.Items, 1)
require.Empty(t, cmp.Diff(resp5.Items[0], allResources[4], cmpIgnoreUnexportedOpts))
// Finished initial pagination phase, request refresh
// Expect no results.
resp6, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp5.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp6.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp6.CompleteListing)
require.Equal(t, resp6.EstimatedItemCount, 5)
require.Empty(t, resp6.DeletedIds)
require.Empty(t, resp6.Items)
// Create some new roles
newR1 := iam.TestRole(t, conn, org.GetPublicId())
newR2 := iam.TestRole(t, conn, org.GetPublicId())
t.Cleanup(func() {
_, err = repo.DeleteRole(ctx, newR1.GetPublicId())
require.NoError(t, err)
_, err = repo.DeleteRole(ctx, newR2.GetPublicId())
require.NoError(t, err)
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
})
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
// Refresh again, should get newR2
resp7, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp6.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp7.CompleteListing)
require.Equal(t, resp7.EstimatedItemCount, 7)
require.Empty(t, resp7.DeletedIds)
require.Len(t, resp7.Items, 1)
require.Empty(t, cmp.Diff(resp7.Items[0], newR2, cmpIgnoreUnexportedOpts))
// Refresh again, should get newR1
resp8, err := iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp7.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp8.CompleteListing)
require.Equal(t, resp8.EstimatedItemCount, 7)
require.Empty(t, resp8.DeletedIds)
require.Len(t, resp8.Items, 1)
require.Empty(t, cmp.Diff(resp8.Items[0], newR1, cmpIgnoreUnexportedOpts))
// Refresh again, should get no results
resp9, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp8.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp9.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp9.CompleteListing)
require.Equal(t, resp9.EstimatedItemCount, 7)
require.Empty(t, resp9.DeletedIds)
require.Empty(t, resp9.Items)
})
t.Run("simple pagination with aggressive filtering", func(t *testing.T) {
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return r.GetPublicId() == allResources[1].GetPublicId() ||
r.GetPublicId() == allResources[len(allResources)-1].GetPublicId(), nil
}
resp, err := iam.ListRoles(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 5)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], allResources[1], cmpIgnoreUnexportedOpts))
resp2, err := iam.ListRolesPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.NotNil(t, resp2.ListToken)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 5)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 1)
require.Empty(t, cmp.Diff(resp2.Items[0], allResources[len(allResources)-1], cmpIgnoreUnexportedOpts))
// request a refresh, nothing should be returned
resp3, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 5)
require.Empty(t, resp3.DeletedIds)
require.Empty(t, resp3.Items)
// Create some new tokens
newR1 := iam.TestRole(t, conn, org.GetPublicId())
newR2 := iam.TestRole(t, conn, org.GetPublicId())
newR3 := iam.TestRole(t, conn, org.GetPublicId())
newR4 := iam.TestRole(t, conn, org.GetPublicId())
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
t.Cleanup(func() {
_, err = repo.DeleteRole(ctx, newR1.GetPublicId())
require.NoError(t, err)
_, err = repo.DeleteRole(ctx, newR2.GetPublicId())
require.NoError(t, err)
_, err = repo.DeleteRole(ctx, newR3.GetPublicId())
require.NoError(t, err)
_, err = repo.DeleteRole(ctx, newR4.GetPublicId())
require.NoError(t, err)
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
})
filterFunc = func(_ context.Context, r *iam.Role) (bool, error) {
return r.GetPublicId() == newR3.GetPublicId() ||
r.GetPublicId() == newR1.GetPublicId(), nil
}
// Refresh again, should get newR3
resp4, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp4.CompleteListing)
require.Equal(t, resp4.EstimatedItemCount, 9)
require.Empty(t, resp4.DeletedIds)
require.Len(t, resp4.Items, 1)
require.Empty(t, cmp.Diff(resp4.Items[0], newR3, cmpIgnoreUnexportedOpts))
// Refresh again, should get newR1
resp5, err := iam.ListRolesRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 9)
require.Empty(t, resp5.DeletedIds)
require.Len(t, resp5.Items, 1)
require.Empty(t, cmp.Diff(resp5.Items[0], newR1, cmpIgnoreUnexportedOpts))
})
t.Run("simple pagination with deletion", func(t *testing.T) {
filterFunc := func(_ context.Context, r *iam.Role) (bool, error) {
return true, nil
}
deletedRoleId := allResources[0].GetPublicId()
_, err := repo.DeleteRole(ctx, deletedRoleId)
require.NoError(t, err)
allResources = allResources[1:]
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
resp, err := iam.ListRoles(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.NotNil(t, resp.ListToken)
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp.CompleteListing)
require.Equal(t, resp.EstimatedItemCount, 4)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], allResources[0], cmpIgnoreUnexportedOpts))
// request remaining results
resp2, err := iam.ListRolesPage(ctx, []byte("some hash"), 3, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 4)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 3)
require.Empty(t, cmp.Diff(resp2.Items, allResources[1:], cmpIgnoreUnexportedOpts))
deletedRoleId = allResources[0].GetPublicId()
_, err = repo.DeleteRole(ctx, deletedRoleId)
require.NoError(t, err)
allResources = allResources[1:]
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
// request a refresh, nothing should be returned except the deleted id
resp3, err := iam.ListRolesRefresh(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId()})
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 3)
require.Contains(t, resp3.DeletedIds, deletedRoleId)
require.Empty(t, resp3.Items)
})
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"time"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
)
// ListRolesPage lists up to page size roles, filtering out entries that
// do not pass the filter item function. It will automatically request
// more roles from the database, at page size chunks, to fill the page.
// It will start its paging based on the information in the token.
// It returns a new list token used to continue pagination or refresh items.
// Roles are ordered by create time descending (most recently created first).
func ListRolesPage(
ctx context.Context,
grantsHash []byte,
pageSize int,
filterItemFn pagination.ListFilterFunc[*Role],
tok *listtoken.Token,
repo *Repository,
withScopeIds []string,
) (*pagination.ListResponse[*Role], error) {
const op = "iam.ListRolesPage"
switch {
case len(grantsHash) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
case pageSize < 1:
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
case filterItemFn == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
case tok == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
case repo == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
case withScopeIds == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
}
if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component")
}
listItemsFn := func(ctx context.Context, lastPageItem *Role, limit int) ([]*Role, time.Time, error) {
opts := []Option{
WithLimit(limit),
}
if lastPageItem != nil {
opts = append(opts, WithStartPageAfterItem(lastPageItem))
} else {
lastItem, err := tok.LastItem(ctx)
if err != nil {
return nil, time.Time{}, err
}
opts = append(opts, WithStartPageAfterItem(lastItem))
}
return repo.listRoles(ctx, withScopeIds, opts...)
}
return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, tok)
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
)
// ListRolesRefresh lists up to page size roles, filtering out entries that
// do not pass the filter item function. It will automatically request
// more roles from the database, at page size chunks, to fill the page.
// It will start its paging based on the information in the token.
// It returns a new list token used to continue pagination or refresh items.
// Roles are ordered by update time descending (most recently updated first).
// Roles may contain items that were already returned during the initial
// pagination phase. It also returns a list of any roles deleted since the
// start of the initial pagination phase or last response.
func ListRolesRefresh(
ctx context.Context,
grantsHash []byte,
pageSize int,
filterItemFn pagination.ListFilterFunc[*Role],
tok *listtoken.Token,
repo *Repository,
withScopeIds []string,
) (*pagination.ListResponse[*Role], error) {
const op = "iam.ListRolesRefresh"
switch {
case len(grantsHash) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
case pageSize < 1:
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
case filterItemFn == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
case tok == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
case repo == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
case withScopeIds == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
}
rt, ok := tok.Subtype.(*listtoken.StartRefreshToken)
if !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a start-refresh token component")
}
listItemsFn := func(ctx context.Context, lastPageItem *Role, limit int) ([]*Role, time.Time, error) {
opts := []Option{
WithLimit(limit),
}
if lastPageItem != nil {
opts = append(opts, WithStartPageAfterItem(lastPageItem))
}
// Add the database read timeout to account for any creations missed due to concurrent
// transactions in the initial pagination phase.
return repo.listRolesRefresh(ctx, rt.PreviousPhaseUpperBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
}
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
// Add the database read timeout to account for any deletions missed due to concurrent
// transactions in previous requests.
return repo.listRoleDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
}
return pagination.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, listDeletedIdsFn, tok)
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package iam
import (
"context"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
)
// ListRolesRefreshPage lists up to page size roles, filtering out entries that
// do not pass the filter item function. It will automatically request
// more roles from the database, at page size chunks, to fill the page.
// It will start its paging based on the information in the token.
// It returns a new list token used to continue pagination or refresh items.
// Roles are ordered by update time descending (most recently updated first).
// Roles may contain items that were already returned during the initial
// pagination phase. It also returns a list of any roles deleted since the
// last response.
func ListRolesRefreshPage(
ctx context.Context,
grantsHash []byte,
pageSize int,
filterItemFn pagination.ListFilterFunc[*Role],
tok *listtoken.Token,
repo *Repository,
withScopeIds []string,
) (*pagination.ListResponse[*Role], error) {
const op = "iam.ListRolesRefreshPage"
switch {
case len(grantsHash) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
case pageSize < 1:
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
case filterItemFn == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
case tok == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
case repo == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
case withScopeIds == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
}
rt, ok := tok.Subtype.(*listtoken.RefreshToken)
if !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a refresh token component")
}
listItemsFn := func(ctx context.Context, lastPageItem *Role, limit int) ([]*Role, time.Time, error) {
opts := []Option{
WithLimit(limit),
}
if lastPageItem != nil {
opts = append(opts, WithStartPageAfterItem(lastPageItem))
} else {
lastItem, err := tok.LastItem(ctx)
if err != nil {
return nil, time.Time{}, err
}
opts = append(opts, WithStartPageAfterItem(lastItem))
}
// Add the database read timeout to account for any creations missed due to concurrent
// transactions in the original list pagination phase.
return repo.listRolesRefresh(ctx, rt.PhaseLowerBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
}
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
// Add the database read timeout to account for any deletes missed due to concurrent
// transactions in the original list pagination phase.
return repo.listRoleDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
}
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, listDeletedIdsFn, tok)
}
@@ -166,10 +166,47 @@ message ListRolesRequest {
string scope_id = 1 [json_name = "scope_id"]; // @gotags: `class:"public" eventstream:"observation"`
bool recursive = 20 [json_name = "recursive"]; // @gotags: `class:"public" eventstream:"observation"`
string filter = 30 [json_name = "filter"]; // @gotags: `class:"public"`
// An opaque token used to continue an existing iteration or
// request updated items. If not specified, pagination
// will start from the beginning.
string list_token = 40 [json_name = "list_token"]; // @gotags: `class:"public"`
// The maximum size of a page in this iteration.
// If unset, the default page size configured will be used.
// If the page_size is greater than the default page configured,
// an error will be returned.
uint32 page_size = 50 [json_name = "page_size"]; // @gotags: `class:"public"`
}
message ListRolesResponse {
repeated resources.roles.v1.Role items = 1;
// The type of response, either "delta" or "complete".
// Delta signifies that this is part of a paginated result
// or an update to a previously completed pagination.
// Complete signifies that it is the last page.
string response_type = 2 [json_name = "response_type"]; // @gotags: `class:"public"`
// An opaque token used to continue an existing pagination or
// request updated items. Use this token in the next list request
// to request the next page.
string list_token = 3 [json_name = "list_token"]; // @gotags: `class:"public"`
// The name of the field which the items are sorted by.
string sort_by = 4 [json_name = "sort_by"]; // @gotags: `class:"public"`
// The direction of the sort, either "asc" or "desc".
string sort_dir = 5 [json_name = "sort_dir"]; // @gotags: `class:"public"`
// A list of item IDs that have been removed since they were returned
// as part of a pagination. They should be dropped from any client cache.
// This may contain items that are not known to the cache, if they were
// created and deleted between listings.
repeated string removed_ids = 6 [json_name = "removed_ids"]; // @gotags: `class:"public"`
// An estimate at the total items available. This may change during pagination.
uint32 est_item_count = 7 [json_name = "est_item_count"]; // @gotags: `class:"public"`
}
message CreateRoleRequest {