Update host sets and auth system to new paradigm (#319)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2020-09-01 12:31:54 -04:00
1 parent 0aba6db720
commit c4522aa813
17 files changed
+2469 -532

No files matched your search

+22 -12
View File
@@ -685,20 +685,30 @@ func (c *Client) NewRequest(ctx context.Context, method, requestPath string, bod
scopeId = "global"
}
scopedPath := strings.TrimPrefix(requestPath, "/")
switch requestPath {
case "scopes":
// This is a special case for creating or listing scopes; don't do
// anything
var finalPath string
switch opts.withNewStyle {
case true:
// Do nothing. Eventually, remove the notion of a scoped client; calls
// to specific resources won't need it, and calls to collections can
// take care of it in the functions themselves as part of the api
// parameters.
finalPath = path.Join(u.Path, "/v1/", requestPath)
default:
// If their path already has 'scopes/' in it, use the given request path
// instead of building it from the client's scope information
if !strings.HasPrefix(scopedPath, "scopes/") {
scopedPath = path.Join("scopes", scopeId, scopedPath)
scopedPath := strings.TrimPrefix(requestPath, "/")
switch requestPath {
case "scopes":
// This is a special case for creating or listing scopes; don't do
// anything
default:
// If their path already has 'scopes/' in it, use the given request path
// instead of building it from the client's scope information
if !strings.HasPrefix(scopedPath, "scopes/") {
scopedPath = path.Join("scopes", scopeId, scopedPath)
}
}
finalPath = path.Join(u.Path, "/v1", scopedPath)
}
scopedPath = path.Join(u.Path, "/v1", scopedPath)
req := &http.Request{
Method: method,
@@ -706,7 +716,7 @@ func (c *Client) NewRequest(ctx context.Context, method, requestPath string, bod
User: u.User,
Scheme: u.Scheme,
Host: host,
Path: scopedPath,
Path: finalPath,
},
Host: u.Host,
}
+430
View File
@@ -0,0 +1,430 @@
package hostsets
import (
"context"
"errors"
"fmt"
"net/url"
"github.com/hashicorp/boundary/api"
"github.com/kr/pretty"
)
func (c *Client) Create2(ctx context.Context, hostCatalogId string, opt ...Option) (*HostSet, *api.Error, error) {
if hostCatalogId == "" {
return nil, nil, fmt.Errorf("empty hostCatalogId value passed into Create request")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts.postMap["host_catalog_id"] = hostCatalogId
req, err := c.client.NewRequest(ctx, "POST", "host-sets", opts.postMap, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating Create request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Create call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Create response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
func (c *Client) Read2(ctx context.Context, hostSetId string, opt ...Option) (*HostSet, *api.Error, error) {
if hostSetId == "" {
return nil, nil, fmt.Errorf("empty hostSetId value passed into Read request")
}
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating Read request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Read call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Read response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
func (c *Client) Update2(ctx context.Context, hostSetId string, version uint32, opt ...Option) (*HostSet, *api.Error, error) {
if hostSetId == "" {
return nil, nil, fmt.Errorf("empty hostSetId value passed into Update request")
}
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
}
existingTarget, existingApiErr, existingErr := c.Read2(ctx, hostSetId, opt...)
if existingErr != nil {
return nil, nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingApiErr != nil {
return nil, nil, fmt.Errorf("error from controller when performing initial check-and-set read: %s", pretty.Sprint(existingApiErr))
}
if existingTarget == nil {
return nil, nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Version
}
opts.postMap["version"] = version
req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-sets/%s", hostSetId), opts.postMap, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating Update request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Update call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Update response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
func (c *Client) Delete2(ctx context.Context, hostSetId string, opt ...Option) (bool, *api.Error, error) {
if hostSetId == "" {
return false, nil, fmt.Errorf("empty hostSetId value passed into Delete request")
}
if c.client == nil {
return false, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...)
if err != nil {
return false, nil, fmt.Errorf("error creating Delete request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return false, nil, fmt.Errorf("error performing client request during Delete call: %w", err)
}
type deleteResponse struct {
Existed bool
}
target := &deleteResponse{}
apiErr, err := resp.Decode(target)
if err != nil {
return false, nil, fmt.Errorf("error decoding Delete response: %w", err)
}
if apiErr != nil {
return false, apiErr, nil
}
return target.Existed, apiErr, nil
}
func (c *Client) List2(ctx context.Context, hostCatalogId string, opt ...Option) ([]*HostSet, *api.Error, error) {
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
req, err := c.client.NewRequest(ctx, "GET", "host-sets", nil, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating List request: %w", err)
}
opts.queryMap["host_catalog_id"] = hostCatalogId
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during List call: %w", err)
}
type listResponse struct {
Items []*HostSet
}
target := &listResponse{}
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding List response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target.Items, apiErr, nil
}
func (c *Client) AddHosts2(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSet, *api.Error, error) {
if hostSetId == "" {
return nil, nil, fmt.Errorf("empty hostSetId value passed into AddHosts request")
}
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, nil, errors.New("zero version number passed into AddHosts request")
}
existingTarget, existingApiErr, existingErr := c.Read2(ctx, hostSetId, opt...)
if existingErr != nil {
return nil, nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingApiErr != nil {
return nil, nil, fmt.Errorf("error from controller when performing initial check-and-set read: %s", pretty.Sprint(existingApiErr))
}
if existingTarget == nil {
return nil, nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Version
}
opts.postMap["version"] = version
if len(hostIds) > 0 {
opts.postMap["host_ids"] = hostIds
}
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:add-hosts", hostSetId), opts.postMap, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating AddHosts request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during AddHosts call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding AddHosts response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
func (c *Client) SetHosts2(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSet, *api.Error, error) {
if hostSetId == "" {
return nil, nil, fmt.Errorf("empty hostSetId value passed into SetHosts request")
}
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, nil, errors.New("zero version number passed into SetHosts request")
}
existingTarget, existingApiErr, existingErr := c.Read2(ctx, hostSetId, opt...)
if existingErr != nil {
return nil, nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingApiErr != nil {
return nil, nil, fmt.Errorf("error from controller when performing initial check-and-set read: %s", pretty.Sprint(existingApiErr))
}
if existingTarget == nil {
return nil, nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Version
}
opts.postMap["version"] = version
if len(hostIds) > 0 {
opts.postMap["host_ids"] = hostIds
} else if hostIds != nil {
// In this function, a non-nil but empty list means clear out
opts.postMap["host_ids"] = nil
}
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:set-hosts", hostSetId), opts.postMap, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating SetHosts request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during SetHosts call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding SetHosts response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
func (c *Client) RemoveHosts2(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSet, *api.Error, error) {
if hostSetId == "" {
return nil, nil, fmt.Errorf("empty hostSetId value passed into RemoveHosts request")
}
if c.client == nil {
return nil, nil, fmt.Errorf("nil client")
}
opts, apiOpts := getOpts(opt...)
apiOpts = append(apiOpts, api.WithNewStyle())
if version == 0 {
if !opts.withAutomaticVersioning {
return nil, nil, errors.New("zero version number passed into RemoveHosts request")
}
existingTarget, existingApiErr, existingErr := c.Read2(ctx, hostSetId, opt...)
if existingErr != nil {
return nil, nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
}
if existingApiErr != nil {
return nil, nil, fmt.Errorf("error from controller when performing initial check-and-set read: %s", pretty.Sprint(existingApiErr))
}
if existingTarget == nil {
return nil, nil, errors.New("nil resource found when performing initial check-and-set read")
}
version = existingTarget.Version
}
opts.postMap["version"] = version
if len(hostIds) > 0 {
opts.postMap["host_ids"] = hostIds
}
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:remove-hosts", hostSetId), opts.postMap, apiOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating RemoveHosts request: %w", err)
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during RemoveHosts call: %w", err)
}
target := new(HostSet)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding RemoveHosts response: %w", err)
}
if apiErr != nil {
return nil, apiErr, nil
}
return target, apiErr, nil
}
+227 -163
View File
@@ -12,108 +12,133 @@ import (
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/servers/controller"
"github.com/kr/pretty"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCustom(t *testing.T) {
assert, require := assert.New(t), require.New(t)
amId := "ampw_1234567890"
tc := controller.NewTestController(t, &controller.TestControllerOpts{
DisableAuthorizationFailures: true,
DefaultAuthMethodId: amId,
DefaultLoginName: "user",
DefaultPassword: "passpass",
})
defer tc.Shutdown()
for _, newStyle := range []bool{false} {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
_, proj := iam.TestScopes(t, tc.IamRepo())
client := tc.Client().Clone()
client.SetScopeId(proj.GetPublicId())
token := tc.Token()
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
client := tc.Client().Clone()
client.SetScopeId(proj.GetPublicId())
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
hClient := hosts.NewClient(client)
h1, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithStaticHostAddress("someaddress"))
require.NoError(err)
require.Nil(apiErr)
h2, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithStaticHostAddress("someaddress"))
require.NoError(err)
require.Nil(apiErr)
hClient := hosts.NewClient(client)
h1, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithStaticHostAddress("someaddress"))
require.NoError(err)
require.Nil(apiErr)
h2, apiErr, err := hClient.Create(tc.Context(), hc.Id, hosts.WithStaticHostAddress("someaddress"))
require.NoError(err)
require.Nil(apiErr)
hSetClient := hostsets.NewClient(client)
hSet, apiErr, err := hSetClient.Create(tc.Context(), hc.Id)
require.NoError(err)
require.Nil(apiErr)
hSetClient := hostsets.NewClient(client)
var hSet *hostsets.HostSet
if newStyle {
hSet, apiErr, err = hSetClient.Create2(tc.Context(), hc.Id)
} else {
hSet, apiErr, err = hSetClient.Create(tc.Context(), hc.Id)
}
require.NoError(err)
require.Nil(apiErr)
hSet, apiErr, err = hSetClient.AddHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id, h2.Id})
require.NoError(err)
require.Nil(apiErr)
assert.Contains(hSet.HostIds, h1.Id, h2.Id)
if newStyle {
hSet, apiErr, err = hSetClient.AddHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id, h2.Id})
} else {
hSet, apiErr, err = hSetClient.AddHosts2(tc.Context(), hSet.Id, hSet.Version, []string{h1.Id, h2.Id})
}
require.NoError(err)
require.Nil(apiErr)
assert.Contains(hSet.HostIds, h1.Id, h2.Id)
hSet, apiErr, err = hSetClient.SetHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id})
require.NoError(err)
require.Nil(apiErr)
assert.ElementsMatch([]string{h1.Id}, hSet.HostIds)
if newStyle {
hSet, apiErr, err = hSetClient.SetHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id})
} else {
hSet, apiErr, err = hSetClient.SetHosts2(tc.Context(), hSet.Id, hSet.Version, []string{h1.Id})
}
require.NoError(err)
require.Nil(apiErr, pretty.Sprint(apiErr))
assert.ElementsMatch([]string{h1.Id}, hSet.HostIds)
hSet, apiErr, err = hSetClient.RemoveHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id})
require.NoError(err)
require.Nil(apiErr)
assert.Empty(hSet.HostIds)
if newStyle {
hSet, apiErr, err = hSetClient.RemoveHosts(tc.Context(), hc.Id, hSet.Id, hSet.Version, []string{h1.Id})
} else {
hSet, apiErr, err = hSetClient.RemoveHosts2(tc.Context(), hSet.Id, hSet.Version, []string{h1.Id})
}
require.NoError(err)
require.Nil(apiErr)
assert.Empty(hSet.HostIds)
}
}
func TestSet_List(t *testing.T) {
assert, require := assert.New(t), require.New(t)
amId := "ampw_1234567890"
tc := controller.NewTestController(t, &controller.TestControllerOpts{
DisableAuthorizationFailures: true,
DefaultAuthMethodId: amId,
DefaultLoginName: "user",
DefaultPassword: "passpass",
})
defer tc.Shutdown()
for _, newStyle := range []bool{false, true} {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
_, proj := iam.TestScopes(t, tc.IamRepo())
client.SetScopeId(proj.GetPublicId())
client := tc.Client()
token := tc.Token()
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
client.SetScopeId(proj.GetPublicId())
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
hClient := hostsets.NewClient(client)
hClient := hostsets.NewClient(client)
var ul []*hostsets.HostSet
ul, apiErr, err := hClient.List(tc.Context(), hc.Id)
assert.NoError(err)
assert.Nil(apiErr)
assert.Empty(ul)
if newStyle {
ul, apiErr, err = hClient.List2(tc.Context(), hc.Id)
} else {
ul, apiErr, err = hClient.List(tc.Context(), hc.Id)
}
require.NoError(err)
require.Nil(apiErr)
assert.Empty(ul)
var expected []*hostsets.HostSet
for i := 0; i < 10; i++ {
expected = append(expected, &hostsets.HostSet{Name: fmt.Sprint(i)})
var expected []*hostsets.HostSet
for i := 0; i < 10; i++ {
expected = append(expected, &hostsets.HostSet{Name: fmt.Sprint(i)})
}
expected[0], apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName(expected[0].Name))
require.NoError(err)
require.Nil(apiErr)
if newStyle {
ul, apiErr, err = hClient.List2(tc.Context(), hc.Id)
} else {
ul, apiErr, err = hClient.List(tc.Context(), hc.Id)
}
require.NoError(err)
require.Nil(apiErr)
assert.ElementsMatch(comparableSetSlice(expected[:1]), comparableSetSlice(ul))
for i := 1; i < 10; i++ {
expected[i], apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName(expected[i].Name))
require.NoError(err)
require.Nil(apiErr)
}
if newStyle {
ul, apiErr, err = hClient.List2(tc.Context(), hc.Id)
} else {
ul, apiErr, err = hClient.List(tc.Context(), hc.Id)
}
require.NoError(err)
require.Nil(apiErr)
assert.ElementsMatch(comparableSetSlice(expected), comparableSetSlice(ul))
}
expected[0], apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName(expected[0].Name))
assert.NoError(err)
assert.Nil(apiErr)
ul, apiErr, err = hClient.List(tc.Context(), hc.Id)
assert.NoError(err)
assert.Nil(apiErr)
assert.ElementsMatch(comparableSetSlice(expected[:1]), comparableSetSlice(ul))
for i := 1; i < 10; i++ {
expected[i], apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName(expected[i].Name))
assert.NoError(err)
assert.Nil(apiErr)
}
ul, apiErr, err = hClient.List(tc.Context(), hc.Id)
require.NoError(err)
assert.Nil(apiErr)
assert.ElementsMatch(comparableSetSlice(expected), comparableSetSlice(ul))
}
func comparableSetSlice(in []*hostsets.HostSet) []hostsets.HostSet {
@@ -132,103 +157,142 @@ func comparableSetSlice(in []*hostsets.HostSet) []hostsets.HostSet {
}
func TestSet_Crud(t *testing.T) {
assert, require := assert.New(t), require.New(t)
amId := "ampw_1234567890"
tc := controller.NewTestController(t, &controller.TestControllerOpts{
DisableAuthorizationFailures: true,
DefaultAuthMethodId: amId,
DefaultLoginName: "user",
DefaultPassword: "passpass",
})
defer tc.Shutdown()
for _, newStyle := range []bool{true} {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
org, proj := iam.TestScopes(t, tc.IamRepo())
client.SetScopeId(org.GetPublicId())
projClient := client.Clone()
projClient.SetScopeId(proj.GetPublicId())
client := tc.Client()
token := tc.Token()
org, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
client.SetScopeId(org.GetPublicId())
projClient := client.Clone()
projClient.SetScopeId(proj.GetPublicId())
hc, apiErr, err := hostcatalogs.NewClient(projClient).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
hc, apiErr, err := hostcatalogs.NewClient(projClient).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
checkHost := func(step string, h *hostsets.HostSet, apiErr *api.Error, err error, wantedName string, wantVersion uint32) {
require.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
checkHost := func(t *testing.T, step string, h *hostsets.HostSet, apiErr *api.Error, err error, wantedName string, wantVersion uint32) {
t.Helper()
require.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != "" {
t.Errorf("ApiError message: %q", apiErr.Message)
}
assert.NotNil(h, "returned no resource", step)
gotName := ""
if h.Name != "" {
gotName = h.Name
}
assert.Equal(wantedName, gotName, step)
assert.Equal(wantVersion, h.Version)
}
assert.NotNil(h, "returned no resource", step)
gotName := ""
if h.Name != "" {
gotName = h.Name
hClient := hostsets.NewClient(projClient)
var h *hostsets.HostSet
if newStyle {
h, apiErr, err = hClient.Create2(tc.Context(), hc.Id, hostsets.WithName("foo"))
} else {
h, apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
}
assert.Equal(wantedName, gotName, step)
assert.Equal(wantVersion, h.Version)
checkHost(t, "create", h, apiErr, err, "foo", 1)
if newStyle {
h, apiErr, err = hClient.Read2(tc.Context(), h.Id)
} else {
h, apiErr, err = hClient.Read(tc.Context(), hc.Id, h.Id)
}
checkHost(t, "read", h, apiErr, err, "foo", 1)
if newStyle {
h, apiErr, err = hClient.Update2(tc.Context(), h.Id, h.Version, hostsets.WithName("bar"))
} else {
h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hostsets.WithName("bar"))
}
checkHost(t, "update", h, apiErr, err, "bar", 2)
if newStyle {
h, apiErr, err = hClient.Update2(tc.Context(), h.Id, h.Version, hostsets.DefaultName())
} else {
h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hostsets.DefaultName())
}
checkHost(t, "update", h, apiErr, err, "", 3)
var existed bool
if newStyle {
existed, apiErr, err = hClient.Delete2(tc.Context(), h.Id)
} else {
existed, apiErr, err = hClient.Delete(tc.Context(), hc.Id, h.Id)
}
assert.NoError(err)
assert.True(existed, "Expected existing catalog when deleted, but it wasn't.")
if newStyle {
existed, apiErr, err = hClient.Delete2(tc.Context(), h.Id)
} else {
existed, apiErr, err = hClient.Delete(tc.Context(), hc.Id, h.Id)
}
assert.NoError(err)
assert.False(existed, "Expected catalog to not exist when deleted, but it did.")
}
hClient := hostsets.NewClient(projClient)
h, apiErr, err := hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
checkHost("create", h, apiErr, err, "foo", 1)
h, apiErr, err = hClient.Read(tc.Context(), hc.Id, h.Id)
checkHost("read", h, apiErr, err, "foo", 1)
h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hostsets.WithName("bar"))
checkHost("update", h, apiErr, err, "bar", 2)
h, apiErr, err = hClient.Update(tc.Context(), hc.Id, h.Id, h.Version, hostsets.DefaultName())
checkHost("update", h, apiErr, err, "", 3)
existed, apiErr, err := hClient.Delete(tc.Context(), hc.Id, h.Id)
assert.NoError(err)
assert.True(existed, "Expected existing catalog when deleted, but it wasn't.")
existed, apiErr, err = hClient.Delete(tc.Context(), hc.Id, h.Id)
assert.NoError(err)
assert.False(existed, "Expected catalog to not exist when deleted, but it did.")
}
// TODO: Get better coverage for expected errors and error formats.
func TestSet_Errors(t *testing.T) {
assert, require := assert.New(t), require.New(t)
amId := "ampw_1234567890"
tc := controller.NewTestController(t, &controller.TestControllerOpts{
DisableAuthorizationFailures: true,
DefaultAuthMethodId: amId,
DefaultLoginName: "user",
DefaultPassword: "passpass",
})
defer tc.Shutdown()
for _, newStyle := range []bool{false, true} {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
_, proj := iam.TestScopes(t, tc.IamRepo())
client.SetScopeId(proj.GetPublicId())
client := tc.Client()
token := tc.Token()
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
client.SetScopeId(proj.GetPublicId())
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
hc, apiErr, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static")
require.NoError(err)
require.Nil(apiErr)
require.NotNil(hc)
hClient := hostsets.NewClient(client)
hClient := hostsets.NewClient(client)
h, apiErr, err := hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
require.NoError(err)
require.Nil(apiErr)
assert.NotNil(h)
var h *hostsets.HostSet
if newStyle {
h, apiErr, err = hClient.Create2(tc.Context(), hc.Id, hostsets.WithName("foo"))
} else {
h, apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
}
require.NoError(err)
require.Nil(apiErr)
assert.NotNil(h)
_, apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
require.NoError(err)
assert.NotNil(apiErr)
if newStyle {
h, apiErr, err = hClient.Create2(tc.Context(), hc.Id, hostsets.WithName("foo"))
} else {
h, apiErr, err = hClient.Create(tc.Context(), hc.Id, hostsets.WithName("foo"))
}
require.NoError(err)
assert.NotNil(apiErr)
assert.Nil(h)
_, apiErr, err = hClient.Read(tc.Context(), hc.Id, static.HostSetPrefix+"_doesntexis")
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(apiErr.Status, http.StatusNotFound)
if newStyle {
_, apiErr, err = hClient.Read2(tc.Context(), static.HostSetPrefix+"_doesntexis")
} else {
_, apiErr, err = hClient.Read(tc.Context(), hc.Id, static.HostSetPrefix+"_doesntexis")
}
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusForbidden, apiErr.Status)
_, apiErr, err = hClient.Read(tc.Context(), hc.Id, "invalid id")
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusForbidden, apiErr.Status)
if newStyle {
_, apiErr, err = hClient.Read2(tc.Context(), "invalid id")
} else {
_, apiErr, err = hClient.Read(tc.Context(), hc.Id, "invalid id")
}
require.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Status)
}
}
+8 -1
View File
@@ -13,7 +13,8 @@ type Option func(*options)
// options = how options are represented
type options struct {
withScopeId string
withScopeId string
withNewStyle bool
}
func getDefaultOptions() options {
@@ -27,3 +28,9 @@ func WithScopeId(id string) Option {
o.withScopeId = id
}
}
func WithNewStyle() Option {
return func(o *options) {
o.withNewStyle = true
}
}
+37 -16
View File
@@ -56,9 +56,12 @@ type RequestInfo struct {
Token string
TokenFormat TokenFormat
// This is used for operations on the scopes collection
scopeIdOverride string
userIdOverride string
// These are set by the service handlers via options
scopeId string
pin string
// This is used by the scopes collection
userIdOverride string
// The following are useful for tests
DisableAuthzFailures bool
@@ -117,10 +120,16 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
// context we won't catch in tests
panic("no verifier information found in context")
}
v.ctx = ctx
opts := getOpts(opt...)
ret.Scope = new(scopes.ScopeInfo)
if v.requestInfo.DisableAuthEntirely {
ret.Scope.Id = v.requestInfo.scopeIdOverride
ret.Scope.Id = v.requestInfo.scopeId
if ret.Scope.Id == "" {
ret.Scope.Id = opts.withScopeId
}
switch {
case ret.Scope.Id == "global":
ret.Scope.Type = "global"
@@ -140,11 +149,21 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
return
}
v.ctx = ctx
v.requestInfo.scopeIdOverride = opts.withScopeId
if err := v.parseAuthParams(); err != nil {
v.logger.Trace("error reading auth parameters from URL", "url", v.requestInfo.Path, "method", v.requestInfo.Method, "error", err)
return
// check if it's new style
if opts.withAction != action.Unknown {
v.act = opts.withAction
v.res = &perms.Resource{
ScopeId: opts.withScopeId,
Id: opts.withId,
Pin: opts.withPin,
}
} else {
// This remains for legacy
v.requestInfo.scopeId = opts.withScopeId
if err := v.parseAuthParams(); err != nil {
v.logger.Trace("error reading auth parameters from URL", "url", v.requestInfo.Path, "method", v.requestInfo.Method, "error", err)
return
}
}
if v.res == nil {
v.logger.Trace("got nil resource information after decorating auth parameters")
@@ -179,12 +198,9 @@ func (v *verifier) parseAuthParams() error {
splitPath := strings.Split(strings.TrimPrefix(trimmedPath, "v1/"), "/")
splitLen := len(splitPath)
// It must be at least length 1 and the first segment must be "scopes"
switch {
case splitLen == 0:
// It must be at least length 1. If so, figure out new vs. legacy parsing
if len(splitPath) == 0 {
return fmt.Errorf("parse auth params: invalid path")
case splitPath[0] != "scopes":
return fmt.Errorf("parse auth params: invalid first segment %q", splitPath[0])
}
for i := 1; i < splitLen; i++ {
@@ -193,6 +209,11 @@ func (v *verifier) parseAuthParams() error {
}
}
// The first segment must be "scopes"
if splitPath[0] != "scopes" {
return fmt.Errorf("parse auth params: invalid first segment %q", splitPath[0])
}
v.act = action.Unknown
v.res = &perms.Resource{
// Start out with scope, and replace when we walk backwards if it's
@@ -243,10 +264,10 @@ func (v *verifier) parseAuthParams() error {
if v.act == action.Read {
v.act = action.List
}
if v.requestInfo.scopeIdOverride == "" {
if v.requestInfo.scopeId == "" {
return errors.New("parse auth params: missing scope ID information for scopes collection operation")
}
v.res.ScopeId = v.requestInfo.scopeIdOverride
v.res.ScopeId = v.requestInfo.scopeId
return nil
case 2:
+25 -1
View File
@@ -1,6 +1,9 @@
package auth
import "github.com/hashicorp/boundary/internal/kms"
import (
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/action"
)
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
@@ -16,6 +19,9 @@ type Option func(*options)
// options = how options are represented
type options struct {
withScopeId string
withPin string
withId string
withAction action.Type
withUserId string
withKms *kms.Kms
}
@@ -33,6 +39,24 @@ func WithScopeId(id string) Option {
}
}
func WithPin(pin string) Option {
return func(o *options) {
o.withPin = pin
}
}
func WithId(id string) Option {
return func(o *options) {
o.withId = id
}
}
func WithAction(action action.Type) Option {
return func(o *options) {
o.withAction = action
}
}
func WithUserId(id string) Option {
return func(o *options) {
o.withUserId = id
+2 -1
View File
@@ -6,7 +6,8 @@ import "context"
func DisabledAuthTestContext(opt ...Option) context.Context {
reqInfo := RequestInfo{DisableAuthEntirely: true}
opts := getOpts(opt...)
reqInfo.scopeIdOverride = opts.withScopeId
reqInfo.scopeId = opts.withScopeId
reqInfo.pin = opts.withPin
reqInfo.userIdOverride = opts.withUserId
return NewVerifierContext(context.Background(), nil, nil, nil, nil, opts.withKms, reqInfo)
}
+283 -8
View File
@@ -16,6 +16,281 @@
"application/json"
],
"paths": {
"/v1/host-sets": {
"get": {
"summary": "List all HostSets under the specific catalog",
"operationId": "HostSetService_ListHostSets",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/controller.api.services.v1.ListHostSetsResponse"
}
}
},
"parameters": [
{
"name": "host_catalog_id",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "view",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
},
"post": {
"summary": "Create a HostSet",
"operationId": "HostSetService_CreateHostSet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
},
{
"name": "host_catalog_id",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
}
},
"/v1/host-sets/{id}": {
"get": {
"summary": "Get a single HostSet",
"operationId": "HostSetService_GetHostSet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "host_catalog_id",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "view",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
},
"delete": {
"summary": "Delete a HostSet",
"operationId": "HostSetService_DeleteHostSet",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/controller.api.services.v1.DeleteHostSetResponse"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "host_catalog_id",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
},
"patch": {
"summary": "Update a HostSet",
"operationId": "HostSetService_UpdateHostSet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
},
{
"name": "host_catalog_id",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "update_mask",
"in": "query",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi"
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
}
},
"/v1/host-sets/{id}:add-hosts": {
"post": {
"summary": "Adds existing Hosts to a Host Set.",
"operationId": "HostSetService_AddHostSetHosts",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.services.v1.AddHostSetHostsRequest"
}
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
}
},
"/v1/host-sets/{id}:remove-hosts": {
"post": {
"summary": "Removes Hosts from the Host Set.",
"operationId": "HostSetService_RemoveHostSetHosts",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "id",
"description": "This host set's id.",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.services.v1.RemoveHostSetHostsRequest"
}
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
}
},
"/v1/host-sets/{id}:set-hosts": {
"post": {
"summary": "Sets the Hosts on the Host Set.",
"operationId": "HostSetService_SetHostSetHosts",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.hostsets.v1.HostSet"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.services.v1.SetHostSetHostsRequest"
}
}
],
"tags": [
"controller.api.services.v1.HostSetService"
]
}
},
"/v1/scopes": {
"get": {
"summary": "Lists all Scopes within the scope of the token making the request",
@@ -894,7 +1169,7 @@
"/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets": {
"get": {
"summary": "List all HostSets under the specific catalog",
"operationId": "HostSetService_ListHostSets",
"operationId": "HostSetService_ListHostSets2",
"responses": {
"200": {
"description": "A successful response.",
@@ -923,7 +1198,7 @@
},
"post": {
"summary": "Create a HostSet",
"operationId": "HostSetService_CreateHostSet",
"operationId": "HostSetService_CreateHostSet2",
"responses": {
"200": {
"description": "",
@@ -956,7 +1231,7 @@
"/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}": {
"get": {
"summary": "Get a single HostSet",
"operationId": "HostSetService_GetHostSet",
"operationId": "HostSetService_GetHostSet2",
"responses": {
"200": {
"description": "",
@@ -991,7 +1266,7 @@
},
"delete": {
"summary": "Delete a HostSet",
"operationId": "HostSetService_DeleteHostSet",
"operationId": "HostSetService_DeleteHostSet2",
"responses": {
"200": {
"description": "A successful response.",
@@ -1020,7 +1295,7 @@
},
"patch": {
"summary": "Update a HostSet",
"operationId": "HostSetService_UpdateHostSet",
"operationId": "HostSetService_UpdateHostSet2",
"responses": {
"200": {
"description": "",
@@ -1069,7 +1344,7 @@
"/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:add-hosts": {
"post": {
"summary": "Adds existing Hosts to a Host Set.",
"operationId": "HostSetService_AddHostSetHosts",
"operationId": "HostSetService_AddHostSetHosts2",
"responses": {
"200": {
"description": "",
@@ -1108,7 +1383,7 @@
"/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:remove-hosts": {
"post": {
"summary": "Removes Hosts from the Host Set.",
"operationId": "HostSetService_RemoveHostSetHosts",
"operationId": "HostSetService_RemoveHostSetHosts2",
"responses": {
"200": {
"description": "",
@@ -1148,7 +1423,7 @@
"/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:set-hosts": {
"post": {
"summary": "Sets the Hosts on the Host Set.",
"operationId": "HostSetService_SetHostSetHosts",
"operationId": "HostSetService_SetHostSetHosts2",
"responses": {
"200": {
"description": "",
@@ -1065,135 +1065,151 @@ var file_controller_api_services_v1_host_set_service_proto_rawDesc = []byte{
0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x68,
0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xc8, 0x0e, 0x0a, 0x0e, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xcf, 0x01, 0x0a, 0x0a,
0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xcd, 0x10, 0x0a, 0x0e, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xeb, 0x01, 0x0a, 0x0a,
0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 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, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x62, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x43, 0x12, 0x3b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68,
0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x16, 0x12, 0x14, 0x47, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73,
0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xe2, 0x01,
0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 0x2f,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x38, 0x12, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61,
0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74,
0x73, 0x92, 0x41, 0x2e, 0x12, 0x2c, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x48,
0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68,
0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x63, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x12, 0xd5, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7e, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x5f, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x5a, 0x43, 0x12, 0x3b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f,
0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f,
0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x92, 0x41, 0x16, 0x12, 0x14, 0x47, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c,
0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xf4, 0x01, 0x0a, 0x0c, 0x4c, 0x69,
0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74,
0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x49, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74,
0x2d, 0x73, 0x65, 0x74, 0x73, 0x5a, 0x38, 0x12, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f,
0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x92,
0x41, 0x2e, 0x12, 0x2c, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x73, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20,
0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
0x12, 0xf2, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 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,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x44, 0x22, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68,
0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x12, 0x12, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xda, 0x01, 0x0a, 0x0d, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x49, 0x32, 0x3b, 0x2f, 0x76, 0x31, 0x2f, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61,
0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x92, 0x41, 0x12, 0x12, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xce, 0x01, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48,
0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x2a, 0x3b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x61, 0x22,
0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x5a, 0x44, 0x22, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70,
0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b,
0x69, 0x64, 0x7d, 0x92, 0x41, 0x12, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61,
0x20, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xf9, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x32, 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, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x92, 0x41, 0x12, 0x12, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f,
0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xfd, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x86, 0x01, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x6b, 0x32, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d,
0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x5a,
0x49, 0x32, 0x3b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68,
0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x92, 0x41, 0x12, 0x12, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f,
0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xe4, 0x01, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6e, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x53, 0x2a, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73,
0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x5a, 0x3d, 0x2a, 0x3b, 0x2f, 0x76, 0x31, 0x2f,
0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61,
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65,
0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, 0x12, 0x12, 0x10, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0xa3, 0x02, 0x0a,
0x0f, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73,
0x12, 0x32, 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, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x50, 0x22, 0x45, 0x2f,
0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74,
0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f,
0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74,
0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68,
0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x24,
0x12, 0x22, 0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20,
0x48, 0x6f, 0x73, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20,
0x53, 0x65, 0x74, 0x2e, 0x12, 0xf6, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74,
0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x32, 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, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74,
0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73,
0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x7a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x50, 0x22, 0x45, 0x2f, 0x76, 0x31, 0x2f, 0x73,
0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61,
0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x73,
0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x21, 0x12, 0x1f, 0x53, 0x65,
0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x20, 0x6f, 0x6e, 0x20,
0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, 0x74, 0x2e, 0x12, 0x83, 0x02,
0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48,
0x6f, 0x73, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48,
0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 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, 0x48,
0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x7e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x53, 0x22, 0x48, 0x2f, 0x76, 0x31,
0x31, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa6, 0x01, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x79, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x73,
0x3a, 0x01, 0x2a, 0x5a, 0x50, 0x22, 0x45, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f,
0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69,
0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x24, 0x12, 0x22,
0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x6f,
0x73, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65,
0x74, 0x2e, 0x12, 0xa0, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65,
0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x32, 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, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f,
0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53,
0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0xa3, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x79, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f,
0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74,
0x2d, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x5a, 0x50, 0x22, 0x45, 0x2f, 0x76, 0x31,
0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63,
0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61,
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73,
0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x6f, 0x73,
0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x92, 0x41, 0x21, 0x12, 0x1f, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48,
0x6f, 0x73, 0x74, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74,
0x20, 0x53, 0x65, 0x74, 0x2e, 0x12, 0xb0, 0x02, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x36, 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, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x48, 0x6f,
0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xaa, 0x01, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x7f, 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73,
0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d,
0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41,
0x22, 0x12, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x73,
0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53,
0x65, 0x74, 0x2e, 0x42, 0xa6, 0x01, 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, 0x92, 0x41, 0x56, 0x12, 0x50, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x20, 0x41, 0x50, 0x49, 0x12, 0x37, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f,
0x72, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20,
0x77, 0x69, 0x74, 0x68, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73,
0x2e, 0x32, 0x05, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2a, 0x02, 0x02, 0x01, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x68, 0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x5a, 0x53, 0x22, 0x48, 0x2f, 0x76, 0x31, 0x2f,
0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x63, 0x61,
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x7b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65,
0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x68,
0x6f, 0x73, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69,
0x74, 0x65, 0x6d, 0x92, 0x41, 0x22, 0x12, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20,
0x48, 0x6f, 0x73, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48,
0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, 0x74, 0x2e, 0x42, 0xa6, 0x01, 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, 0x92, 0x41, 0x56, 0x12, 0x50, 0x0a, 0x0e, 0x43,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x41, 0x50, 0x49, 0x12, 0x37, 0x41,
0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x20,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69,
0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x73, 0x2e, 0x32, 0x05, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2a, 0x02, 0x02,
0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
File diff suppressed because it is too large. Load diff
+8
View File
@@ -26,6 +26,7 @@ type options struct {
withSkipVetForWrite bool
withDisassociate bool
withSkipRoleCreation bool
withUserId string
withRandomReader io.Reader
}
@@ -119,6 +120,13 @@ func WithSkipRoleCreation(enable bool) Option {
}
}
// WithUserId provides an option to specify the user ID to use when creating roles with new scopes.
func WithUserId(id string) Option {
return func(o *options) {
o.withUserId = id
}
}
// WithRandomReader provides and option to specify a random reader.
func WithRandomReader(reader io.Reader) Option {
return func(o *options) {
+8 -4
View File
@@ -46,20 +46,22 @@ func TestRepo(t *testing.T, conn *gorm.DB, rootWrapper wrapping.Wrapper, opt ...
}
// TestScopes creates an org and project suitable for testing.
func TestScopes(t *testing.T, repo *Repository) (org *Scope, prj *Scope) {
func TestScopes(t *testing.T, repo *Repository, opt ...Option) (org *Scope, prj *Scope) {
t.Helper()
require := require.New(t)
opts := getOpts(opt...)
org, err := NewOrg()
require.NoError(err)
org, err = repo.CreateScope(context.Background(), org, "")
org, err = repo.CreateScope(context.Background(), org, opts.withUserId)
require.NoError(err)
require.NotNil(org)
require.NotEmpty(org.GetPublicId())
prj, err = NewProject(org.GetPublicId())
require.NoError(err)
prj, err = repo.CreateScope(context.Background(), prj, "")
prj, err = repo.CreateScope(context.Background(), prj, opts.withUserId)
require.NoError(err)
require.NotNil(prj)
require.NotEmpty(prj.GetPublicId())
@@ -71,9 +73,11 @@ func TestOrg(t *testing.T, repo *Repository, opt ...Option) (org *Scope) {
t.Helper()
require := require.New(t)
opts := getOpts(opt...)
org, err := NewOrg(opt...)
require.NoError(err)
org, err = repo.CreateScope(context.Background(), org, "")
org, err = repo.CreateScope(context.Background(), org, opts.withUserId)
require.NoError(err)
require.NotNil(org)
require.NotEmpty(org.GetPublicId())
@@ -26,8 +26,12 @@ service HostSetService {
// non existing resource an error is returned.
rpc GetHostSet(GetHostSetRequest) returns (GetHostSetResponse) {
option (google.api.http) = {
get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
get: "/v1/host-sets/{id}"
response_body: "item"
additional_bindings {
get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Get a single HostSet"
@@ -41,7 +45,10 @@ service HostSetService {
// returned.
rpc ListHostSets(ListHostSetsRequest) returns (ListHostSetsResponse) {
option (google.api.http) = {
get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets"
get: "/v1/host-sets"
additional_bindings {
get: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "List all HostSets under the specific catalog"
@@ -56,9 +63,14 @@ service HostSetService {
// error is returned.
rpc CreateHostSet(CreateHostSetRequest) returns (CreateHostSetResponse) {
option (google.api.http) = {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets"
post: "/v1/host-sets"
body: "item"
response_body: "item"
additional_bindings {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets"
body: "item"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Create a HostSet"
@@ -75,9 +87,14 @@ service HostSetService {
// name to one that is already in use in this host catalog.
rpc UpdateHostSet(UpdateHostSetRequest) returns (UpdateHostSetResponse) {
option (google.api.http) = {
patch: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
patch: "/v1/host-sets/{id}"
body: "item"
response_body: "item"
additional_bindings: {
patch: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
body: "item"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Update a HostSet"
@@ -91,7 +108,10 @@ service HostSetService {
// before the DeleteHostSet request was received.
rpc DeleteHostSet(DeleteHostSetRequest) returns (DeleteHostSetResponse) {
option (google.api.http) = {
delete: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
delete: "/v1/host-sets/{id}"
additional_bindings: {
delete: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Delete a HostSet"
@@ -106,9 +126,14 @@ service HostSetService {
// returned.
rpc AddHostSetHosts(AddHostSetHostsRequest) returns (AddHostSetHostsResponse) {
option (google.api.http) = {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:add-hosts"
post: "/v1/host-sets/{id}:add-hosts"
body: "*"
response_body: "item"
additional_bindings: {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:add-hosts"
body: "*"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Adds existing Hosts to a Host Set."
@@ -124,9 +149,14 @@ service HostSetService {
// error is returned.
rpc SetHostSetHosts(SetHostSetHostsRequest) returns (SetHostSetHostsResponse) {
option (google.api.http) = {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:set-hosts"
post: "/v1/host-sets/{id}:set-hosts"
body: "*"
response_body: "item"
additional_bindings: {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:set-hosts"
body: "*"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Sets the Hosts on the Host Set."
@@ -140,9 +170,14 @@ service HostSetService {
// an error is returned.
rpc RemoveHostSetHosts(RemoveHostSetHostsRequest) returns (RemoveHostSetHostsResponse) {
option (google.api.http) = {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:remove-hosts"
post: "/v1/host-sets/{id}:remove-hosts"
body: "*"
response_body: "item"
additional_bindings: {
post: "/v1/scopes/*/host-catalogs/{host_catalog_id}/host-sets/{id}:remove-hosts"
body: "*"
response_body: "item"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Removes Hosts from the Host Set."
@@ -115,6 +115,7 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/scopes/someid/roles/someid",
"v1/scopes/someid/users",
"v1/scopes/someid/users/someid",
"v1/host-sets/someid",
},
"POST": {
// Creation end points
@@ -127,6 +128,7 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/scopes/someid/host-catalogs",
"v1/scopes/someid/host-catalogs/someid/host-sets",
"v1/scopes/someid/host-catalogs/someid/hosts",
"v1/host-sets",
// custom methods
"v1/scopes/someid/auth-methods/someid:authenticate",
@@ -141,6 +143,9 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/scopes/someid/groups/someid:add-members",
"v1/scopes/someid/groups/someid:set-members",
"v1/scopes/someid/groups/someid:remove-members",
"v1/host-sets/someid:add-hosts",
"v1/host-sets/someid:set-hosts",
"v1/host-sets/someid:remove-hosts",
},
"DELETE": {
"v1/scopes/someid",
@@ -153,6 +158,7 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/scopes/someid/host-catalogs/someid",
"v1/scopes/someid/host-catalogs/someid/host-sets/someid",
"v1/scopes/someid/host-catalogs/someid/hosts/someid",
"v1/host-sets/someid",
},
"PATCH": {
"v1/scopes/someid",
@@ -163,6 +169,7 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/scopes/someid/host-catalogs/someid",
"v1/scopes/someid/host-catalogs/someid/host-sets/someid",
"v1/scopes/someid/host-catalogs/someid/hosts/someid",
"v1/host-sets/someid",
},
} {
for _, p := range paths {
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/boundary/internal/host/static/store"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/action"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
@@ -43,14 +44,51 @@ func NewService(repoFn common.StaticRepoFactory) (Service, error) {
return Service{staticRepoFn: repoFn}, nil
}
func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) (*pbs.ListHostSetsResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
func (s Service) getHostStructs(ctx context.Context, id string, isHostSetId bool) (*static.HostCatalog, *static.HostSet, error) {
repo, err := s.staticRepoFn()
if err != nil {
return nil, nil, err
}
hostCatalogId := id
var hostSet *static.HostSet
if isHostSetId {
hostSet, _, err = repo.LookupSet(ctx, id)
if err != nil {
return nil, nil, err
}
if hostSet == nil {
return nil, nil, handlers.ForbiddenError()
}
hostCatalogId = hostSet.GetCatalogId()
}
hostCatalog, err := repo.LookupCatalog(ctx, hostCatalogId)
if err != nil {
return nil, nil, err
}
if hostCatalog == nil {
return nil, nil, handlers.ForbiddenError()
}
return hostCatalog, hostSet, nil
}
func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) (*pbs.ListHostSetsResponse, error) {
if err := validateListRequest(req); err != nil {
return nil, err
}
hostCatalog, _, err := s.getHostStructs(ctx, req.GetHostCatalogId(), false)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.List),
)
if authResults.Error != nil {
return nil, authResults.Error
}
hl, err := s.listFromRepo(ctx, req.GetHostCatalogId())
if err != nil {
return nil, err
@@ -63,13 +101,24 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
// GetHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*pbs.GetHostSetResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateGetRequest(req); err != nil {
return nil, err
}
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.Read),
)
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
@@ -80,34 +129,59 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p
// CreateHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetRequest) (*pbs.CreateHostSetResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateCreateRequest(req); err != nil {
return nil, err
}
h, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetHostCatalogId(), req.GetItem())
hcId := req.GetItem().GetHostCatalogId()
if hcId == "" {
hcId = req.GetHostCatalogId()
}
hostCatalog, _, err := s.getHostStructs(ctx, hcId, false)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.Create),
)
if authResults.Error != nil {
return nil, authResults.Error
}
h, err := s.createInRepo(ctx, authResults.Scope.GetId(), hcId, req.GetItem())
if err != nil {
return nil, err
}
h.Scope = authResults.Scope
return &pbs.CreateHostSetResponse{
Item: h,
Uri: fmt.Sprintf("scopes/%s/host-catalogs/%s/host-sets/%s", authResults.Scope.GetId(), req.GetHostCatalogId(), h.GetId()),
Uri: fmt.Sprintf("scopes/%s/host-catalogs/%s/host-sets/%s", authResults.Scope.GetId(), hcId, h.GetId()),
}, nil
}
// UpdateHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetRequest) (*pbs.UpdateHostSetResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetHostCatalogId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.Update),
)
if authResults.Error != nil {
return nil, authResults.Error
}
hc, err := s.updateInRepo(ctx, authResults.Scope.GetId(), hostCatalog.GetPublicId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
@@ -117,13 +191,23 @@ func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetReques
// DeleteHostSet implements the interface pbs.HostSetServiceServer.
func (s Service) DeleteHostSet(ctx context.Context, req *pbs.DeleteHostSetRequest) (*pbs.DeleteHostSetResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateDeleteRequest(req); err != nil {
return nil, err
}
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.Delete),
)
if authResults.Error != nil {
return nil, authResults.Error
}
existed, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId())
if err != nil {
return nil, err
@@ -133,13 +217,23 @@ func (s Service) DeleteHostSet(ctx context.Context, req *pbs.DeleteHostSetReques
// AddHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRequest) (*pbs.AddHostSetHostsResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateAddRequest(req); err != nil {
return nil, err
}
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.AddHosts),
)
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.addInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
@@ -149,13 +243,23 @@ func (s Service) AddHostSetHosts(ctx context.Context, req *pbs.AddHostSetHostsRe
// SetHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRequest) (*pbs.SetHostSetHostsResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateSetRequest(req); err != nil {
return nil, err
}
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.SetHosts),
)
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.setInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
@@ -165,13 +269,23 @@ func (s Service) SetHostSetHosts(ctx context.Context, req *pbs.SetHostSetHostsRe
// RemoveHostSetHosts implements the interface pbs.HostSetServiceServer.
func (s Service) RemoveHostSetHosts(ctx context.Context, req *pbs.RemoveHostSetHostsRequest) (*pbs.RemoveHostSetHostsResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
return nil, authResults.Error
}
if err := validateRemoveRequest(req); err != nil {
return nil, err
}
hostCatalog, hostSet, err := s.getHostStructs(ctx, req.GetId(), true)
if err != nil {
return nil, err
}
authResults := auth.Verify(ctx,
auth.WithScopeId(hostCatalog.GetScopeId()),
auth.WithId(hostSet.GetPublicId()),
auth.WithPin(hostCatalog.GetPublicId()),
auth.WithAction(action.RemoveHosts),
)
if authResults.Error != nil {
return nil, authResults.Error
}
g, err := s.removeInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetHostIds(), req.GetVersion())
if err != nil {
return nil, err
@@ -366,26 +480,20 @@ func toProto(in *static.HostSet, hs []*static.Host) *pb.HostSet {
// * The type asserted by the ID and/or field is known
// * If relevant, the type derived from the id prefix matches what is claimed by the type field
func validateGetRequest(req *pbs.GetHostSetRequest) error {
return handlers.ValidateGetRequest(static.HostSetPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
return badFields
})
return handlers.ValidateGetRequest(static.HostSetPrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateHostSetRequest) error {
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
hcId := req.GetItem().GetHostCatalogId()
if hcId == "" {
hcId = req.GetHostCatalogId()
}
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
if !handlers.ValidId(static.HostCatalogPrefix, hcId) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if req.GetItem().GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This field is read only."
}
switch host.SubtypeFromId(req.GetHostCatalogId()) {
switch host.SubtypeFromId(hcId) {
case host.StaticSubtype:
if req.GetItem().GetType() != "" && req.GetItem().GetType() != host.StaticSubtype.String() {
badFields["type"] = "Doesn't match the parent resource's type."
@@ -398,12 +506,6 @@ func validateCreateRequest(req *pbs.CreateHostSetRequest) error {
func validateUpdateRequest(req *pbs.UpdateHostSetRequest) error {
return handlers.ValidateUpdateRequest(static.HostSetPrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if req.GetItem().GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This is a read only field and cannot be specified in an update request."
}
if req.GetItem().GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
@@ -412,13 +514,7 @@ func validateUpdateRequest(req *pbs.UpdateHostSetRequest) error {
}
func validateDeleteRequest(req *pbs.DeleteHostSetRequest) error {
return handlers.ValidateDeleteRequest(static.HostSetPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
return badFields
})
return handlers.ValidateDeleteRequest(static.HostSetPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListHostSetsRequest) error {
@@ -434,9 +530,6 @@ func validateListRequest(req *pbs.ListHostSetsRequest) error {
func validateAddRequest(req *pbs.AddHostSetHostsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if !handlers.ValidId(static.HostSetPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
@@ -446,6 +539,12 @@ func validateAddRequest(req *pbs.AddHostSetHostsRequest) error {
if len(req.GetHostIds()) == 0 {
badFields["host_ids"] = "Must be non-empty."
}
for _, id := range req.GetHostIds() {
if !handlers.ValidId(static.HostPrefix, id) {
badFields["host_ids"] = "Incorrectly formatted host identifier."
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
@@ -454,15 +553,18 @@ func validateAddRequest(req *pbs.AddHostSetHostsRequest) error {
func validateSetRequest(req *pbs.SetHostSetHostsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if !handlers.ValidId(static.HostSetPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
badFields["version"] = "Required field."
}
for _, id := range req.GetHostIds() {
if !handlers.ValidId(static.HostPrefix, id) {
badFields["host_ids"] = "Incorrectly formatted host identifier."
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
@@ -471,9 +573,6 @@ func validateSetRequest(req *pbs.SetHostSetHostsRequest) error {
func validateRemoveRequest(req *pbs.RemoveHostSetHostsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if !handlers.ValidId(static.HostSetPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
@@ -483,6 +582,12 @@ func validateRemoveRequest(req *pbs.RemoveHostSetHostsRequest) error {
if len(req.GetHostIds()) == 0 {
badFields["host_ids"] = "Must be non-empty."
}
for _, id := range req.GetHostIds() {
if !handlers.ValidId(static.HostPrefix, id) {
badFields["host_ids"] = "Incorrectly formatted host identifier."
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
@@ -48,9 +48,7 @@ func TestGet(t *testing.T) {
static.TestSetMembers(t, conn, hs.GetPublicId(), h)
hIds := []string{h[0].GetPublicId(), h[1].GetPublicId()}
toMerge := &pbs.GetHostSetRequest{
HostCatalogId: hc.GetPublicId(),
}
toMerge := &pbs.GetHostSetRequest{}
pHost := &pb.HostSet{
HostCatalogId: hc.GetPublicId(),
@@ -78,7 +76,7 @@ func TestGet(t *testing.T) {
name: "Get a non existing Host Set",
req: &pbs.GetHostSetRequest{Id: static.HostSetPrefix + "_DoesntExis"},
res: nil,
errCode: codes.NotFound,
errCode: codes.PermissionDenied,
},
{
name: "Wrong id prefix",
@@ -201,8 +199,7 @@ func TestDelete(t *testing.T) {
name: "Delete an Existing Host",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
HostCatalogId: hc.GetPublicId(),
Id: h.GetPublicId(),
Id: h.GetPublicId(),
},
res: &pbs.DeleteHostSetResponse{
Existed: true,
@@ -213,25 +210,19 @@ func TestDelete(t *testing.T) {
name: "Delete bad id Host",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
HostCatalogId: hc.GetPublicId(),
Id: static.HostSetPrefix + "_doesntexis",
Id: static.HostSetPrefix + "_doesntexis",
},
res: &pbs.DeleteHostSetResponse{
Existed: false,
},
errCode: codes.OK,
res: nil,
errCode: codes.PermissionDenied,
},
{
name: "Delete bad host catalog id in Host",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
HostCatalogId: static.HostCatalogPrefix + "_doesntexis",
Id: h.GetPublicId(),
Id: h.GetPublicId(),
},
res: &pbs.DeleteHostSetResponse{
Existed: false,
},
errCode: codes.OK,
res: nil,
errCode: codes.PermissionDenied,
},
{
name: "Bad Host Id formatting",
@@ -280,9 +271,9 @@ func TestDelete_twice(t *testing.T) {
got, gErr := s.DeleteHostSet(ctx, req)
assert.NoError(gErr, "First attempt")
assert.True(got.GetExisted(), "Expected existed to be true for the first delete.")
got, gErr = s.DeleteHostSet(ctx, req)
assert.NoError(gErr, "Second attempt")
assert.False(got.GetExisted(), "Expected existed to be false for the second delete.")
_, gErr = s.DeleteHostSet(ctx, req)
assert.Error(gErr, "Second attempt")
assert.Equal(codes.PermissionDenied, status.Code(gErr), "Expected permission denied for the second delete.")
}
func TestCreate(t *testing.T) {
@@ -457,8 +448,7 @@ func TestUpdate(t *testing.T) {
hCreated, err := ptypes.Timestamp(hs.GetCreateTime().GetTimestamp())
require.NoError(t, err, "Failed to convert proto to timestamp")
toMerge := &pbs.UpdateHostSetRequest{
HostCatalogId: hc.GetPublicId(),
Id: hs.GetPublicId(),
Id: hs.GetPublicId(),
}
tested, err := host_sets.NewService(repoFn)
@@ -661,7 +651,7 @@ func TestUpdate(t *testing.T) {
Description: &wrappers.StringValue{Value: "desc"},
},
},
errCode: codes.Internal,
errCode: codes.PermissionDenied,
},
{
name: "Cant change Id",
@@ -831,29 +821,17 @@ func TestAddHostSetHosts(t *testing.T) {
{
name: "Bad Set Id",
req: &pbs.AddHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
{
name: "Bad Catalog Id",
req: &pbs.AddHostSetHostsRequest{
HostCatalogId: "bad catalog id",
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
{
name: "Empty host list",
req: &pbs.AddHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
},
errCode: codes.InvalidArgument,
},
@@ -918,10 +896,9 @@ func TestSetHostSetHosts(t *testing.T) {
ss := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
tc.setup(ss)
req := &pbs.SetHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: tc.setHosts,
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: tc.setHosts,
}
got, err := s.SetHostSetHosts(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req)
@@ -940,20 +917,9 @@ func TestSetHostSetHosts(t *testing.T) {
{
name: "Bad Set Id",
req: &pbs.SetHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
{
name: "Bad Catalog Id",
req: &pbs.SetHostSetHostsRequest{
HostCatalogId: "bad catalog id",
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
@@ -1020,10 +986,9 @@ func TestRemoveHostSetHosts(t *testing.T) {
ss := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
tc.setup(ss)
req := &pbs.RemoveHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: tc.removeHosts,
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: tc.removeHosts,
}
got, err := s.RemoveHostSetHosts(auth.DisabledAuthTestContext(auth.WithScopeId(proj.GetPublicId())), req)
@@ -1046,33 +1011,21 @@ func TestRemoveHostSetHosts(t *testing.T) {
req *pbs.RemoveHostSetHostsRequest
errCode codes.Code
}{
{
name: "Bad catalog Id",
req: &pbs.RemoveHostSetHostsRequest{
HostCatalogId: "bad id",
Id: ss.GetPublicId(),
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
{
name: "Bad set Id",
req: &pbs.RemoveHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{hs[0].GetPublicId()},
},
errCode: codes.InvalidArgument,
},
{
name: "empty hosts",
req: &pbs.RemoveHostSetHostsRequest{
HostCatalogId: hc.GetPublicId(),
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{},
Id: "bad id",
Version: ss.GetVersion(),
HostIds: []string{},
},
errCode: codes.InvalidArgument,
},
+43 -4
View File
@@ -7,6 +7,8 @@ import (
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/authtokens"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/iam"
@@ -15,6 +17,13 @@ import (
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/vault/sdk/helper/base62"
"github.com/jinzhu/gorm"
"github.com/kr/pretty"
)
const (
DefaultTestAuthMethodId = "ampw_1234567890"
DefaultTestLoginName = "user"
DefaultTestPassword = "passpass"
)
// TestController wraps a base.Server and Controller to provide a
@@ -30,6 +39,7 @@ type TestController struct {
ctx context.Context
cancel context.CancelFunc
name string
opts *TestControllerOpts
}
// Controller returns the underlying controller
@@ -85,6 +95,28 @@ func (tc *TestController) DbConn() *gorm.DB {
return tc.b.Database
}
func (tc *TestController) Token() *authtokens.AuthToken {
if tc.opts.DisableAuthMethodCreation {
tc.t.Error("no default auth method ID configured")
return nil
}
token, apiErr, err := authmethods.NewClient(tc.Client()).Authenticate(
tc.Context(),
tc.b.DevAuthMethodId,
tc.b.DevLoginName,
tc.b.DevPassword,
)
if err != nil {
tc.t.Error(fmt.Errorf("error logging in: %w", err))
return nil
}
if apiErr != nil {
tc.t.Error(fmt.Errorf("api err from logging in: %s", pretty.Sprint(apiErr)))
return nil
}
return token
}
func (tc *TestController) addrs(purpose string) []string {
var prefix string
switch purpose {
@@ -220,14 +252,15 @@ type TestControllerOpts struct {
func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
ctx, cancel := context.WithCancel(context.Background())
if opts == nil {
opts = new(TestControllerOpts)
}
tc := &TestController{
t: t,
ctx: ctx,
cancel: cancel,
}
if opts == nil {
opts = new(TestControllerOpts)
opts: opts,
}
// Base server
@@ -248,12 +281,18 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
if opts.DefaultAuthMethodId != "" {
tc.b.DevAuthMethodId = opts.DefaultAuthMethodId
} else {
tc.b.DevAuthMethodId = DefaultTestAuthMethodId
}
if opts.DefaultLoginName != "" {
tc.b.DevLoginName = opts.DefaultLoginName
} else {
tc.b.DevLoginName = DefaultTestLoginName
}
if opts.DefaultPassword != "" {
tc.b.DevPassword = opts.DefaultPassword
} else {
tc.b.DevPassword = DefaultTestPassword
}
// Start a logger