Adding Groups API endpoint and SDK (#122)

* feat: Add group grpc handler.

* feat: Add group sdk hooks.

* fix:  Adding forgotten generated group sdk file.

* fix:  Fix a copy/paste error in test msg.

* fix:  Correct assert error msg.

* fix: Remove unused proto imports.
This commit is contained in:
Todd Knight authored and GitHub committed 2020-06-15 15:22:41 -07:00
1 parent ffddf24f86
commit 6ea448a7f3
23 files changed
+3834 -2

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
// Code generated by "make api"; DO NOT EDIT.
package groups
import (
"encoding/json"
"time"
"github.com/fatih/structs"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/internal/strutil"
)
type Group struct {
Client *api.Client `json:"-"`
defaultFields []string
// The ID of the Project
// Output only.
Id string `json:"id,omitempty"`
// Optional name for identification purposes
Name *string `json:"name,omitempty"`
// Optional user-set descripton for identification purposes
Description *string `json:"description,omitempty"`
// The time this resource was created
// Output only.
CreatedTime time.Time `json:"created_time,omitempty"`
// The time this resource was last updated.
// Output only.
UpdatedTime time.Time `json:"updated_time,omitempty"`
// Whether the resource is disabled
Disabled *bool `json:"disabled,omitempty"`
}
func (s *Group) SetDefault(key string) {
s.defaultFields = strutil.AppendIfMissing(s.defaultFields, key)
}
func (s *Group) UnsetDefault(key string) {
s.defaultFields = strutil.StrListDelete(s.defaultFields, key)
}
func (s Group) MarshalJSON() ([]byte, error) {
m := structs.Map(s)
if m == nil {
m = make(map[string]interface{})
}
for _, k := range s.defaultFields {
m[k] = nil
}
return json.Marshal(m)
}
+98
View File
@@ -0,0 +1,98 @@
package groups_test
import (
"net/http"
"testing"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/scopes"
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller"
"github.com/stretchr/testify/assert"
)
func TestGroup_Crud(t *testing.T) {
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
org := &scopes.Organization{
Client: client,
}
checkGroup := func(step string, g *groups.Group, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
}
assert.NotNil(g, "returned no resource", step)
gotName := ""
if g.Name != nil {
gotName = *g.Name
}
assert.Equal(wantedName, gotName, step)
}
g, apiErr, err := org.CreateGroup(tc.Context(), &groups.Group{Name: api.String("foo")})
checkGroup("create", g, apiErr, err, "foo")
g, apiErr, err = org.ReadGroup(tc.Context(), &groups.Group{Id: g.Id})
checkGroup("read", g, apiErr, err, "foo")
g = &groups.Group{Id: g.Id}
g.Name = api.String("bar")
g, apiErr, err = org.UpdateGroup(tc.Context(), g)
checkGroup("update", g, apiErr, err, "bar")
g = &groups.Group{Id: g.Id}
g.SetDefault("name")
g, apiErr, err = org.UpdateGroup(tc.Context(), g)
checkGroup("update", g, apiErr, err, "")
existed, apiErr, err := org.DeleteGroup(tc.Context(), g)
assert.NoError(t, err)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
existed, apiErr, err = org.DeleteGroup(tc.Context(), g)
assert.NoError(t, err)
assert.False(t, existed, "Expected user to not exist when deleted, but it did.")
}
func TestGroup_Errors(t *testing.T) {
assert := assert.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
ctx := tc.Context()
client := tc.Client()
org := &scopes.Organization{
Client: client,
}
u, apiErr, err := org.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
// Create another resource with the same name.
_, apiErr, err = org.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.NotNil(apiErr)
_, apiErr, err = org.ReadGroup(ctx, &groups.Group{Id: iam.GroupPrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
_, apiErr, err = org.ReadGroup(ctx, &groups.Group{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
_, apiErr, err = org.UpdateGroup(ctx, &groups.Group{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
}
+1 -1
View File
@@ -32,7 +32,7 @@ func TestCatalogs_Crud(t *testing.T) {
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
}
assert.NotNil(hc, "returned project", step)
assert.NotNil(hc, "returned no resource", step)
gotName := ""
if hc.Name != nil {
gotName = *hc.Name
+5
View File
@@ -34,6 +34,11 @@ var createFuncs = map[string][]*createInfo{
targetType: "Project",
path: `"projects"`,
},
{
baseType: "Organization",
targetType: "groups.Group",
path: `"groups"`,
},
{
baseType: "Organization",
targetType: "users.User",
+5
View File
@@ -22,6 +22,11 @@ var deleteFuncs = map[string][]*deleteInfo{
"Project",
"projects",
},
{
"Organization",
"groups.Group",
"groups",
},
{
"Organization",
"users.User",
+11
View File
@@ -114,6 +114,17 @@ var inputStructs = []*structInfo{
"",
templateTypeResource,
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/groups/group.pb.go",
"Group",
os.Getenv("GEN_BASEPATH") + "/api/groups/group.gen.go",
"Group",
"groups",
"",
"",
"",
templateTypeResource,
},
{
os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/hosts/host_catalog.pb.go",
"StaticHostCatalogDetails",
+5
View File
@@ -22,6 +22,11 @@ var readFuncs = map[string][]*readInfo{
targetType: "Project",
path: "projects",
},
{
baseType: "Organization",
targetType: "groups.Group",
path: "groups",
},
{
baseType: "Organization",
targetType: "users.User",
+5
View File
@@ -22,6 +22,11 @@ var updateFuncs = map[string][]*updateInfo{
targetType: "Project",
path: "projects",
},
{
baseType: "Organization",
targetType: "groups.Group",
path: "groups",
},
{
baseType: "Organization",
targetType: "users.User",
+39
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/hosts"
"github.com/hashicorp/watchtower/api/users"
)
@@ -49,6 +50,44 @@ func (s Organization) CreateProject(ctx context.Context, r *Project) (*Project,
return target, apiErr, nil
}
func (s Organization) CreateGroup(ctx context.Context, r *groups.Group) (*groups.Group, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in Creategroups.Group request")
}
if s.Id == "" {
// Assume the client has been configured with organization already and
// move on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "org", s.Id)
}
req, err := s.Client.NewRequest(ctx, "POST", "groups", r)
if err != nil {
return nil, nil, fmt.Errorf("error creating Creategroups.Group request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Creategroups.Group call: %w", err)
}
target := new(groups.Group)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Creategroups.Group repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
func (s Organization) CreateUser(ctx context.Context, r *users.User) (*users.User, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in Createusers.User request")
+45
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/hosts"
"github.com/hashicorp/watchtower/api/users"
)
@@ -54,6 +55,50 @@ func (s Organization) DeleteProject(ctx context.Context, r *Project) (bool, *api
return target.Existed, apiErr, nil
}
// DeleteGroup returns true iff the groups.Group existed when the delete attempt was made.
func (s Organization) DeleteGroup(ctx context.Context, r *groups.Group) (bool, *api.Error, error) {
if s.Client == nil {
return false, nil, fmt.Errorf("nil client in DeleteGroup request")
}
if s.Id == "" {
// Assume the client has been configured with organization already and
// move on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "org", s.Id)
}
if r.Id == "" {
return false, nil, fmt.Errorf("empty groups.Group ID field in DeleteGroup request")
}
req, err := s.Client.NewRequest(ctx, "DELETE", fmt.Sprintf("%s/%s", "groups", r.Id), nil)
if err != nil {
return false, nil, fmt.Errorf("error creating DeleteGroup request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return false, nil, fmt.Errorf("error performing client request during DeleteGroup 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 DeleteGroup repsonse: %w", err)
}
return target.Existed, apiErr, nil
}
// DeleteUser returns true iff the users.User existed when the delete attempt was made.
func (s Organization) DeleteUser(ctx context.Context, r *users.User) (bool, *api.Error, error) {
if s.Client == nil {
+42
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/hosts"
"github.com/hashicorp/watchtower/api/users"
)
@@ -52,6 +53,47 @@ func (s Organization) ReadProject(ctx context.Context, r *Project) (*Project, *a
return target, apiErr, nil
}
func (s Organization) ReadGroup(ctx context.Context, r *groups.Group) (*groups.Group, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadGroup request")
}
if s.Id == "" {
// Assume the client has been configured with organization already and
// move on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "org", s.Id)
}
if r.Id == "" {
return nil, nil, fmt.Errorf("empty groups.Group ID field in ReadGroup request")
}
req, err := s.Client.NewRequest(ctx, "GET", fmt.Sprintf("%s/%s", "groups", r.Id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating ReadGroup request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during ReadGroup call: %w", err)
}
target := new(groups.Group)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding ReadGroup repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
func (s Organization) ReadUser(ctx context.Context, r *users.User) (*users.User, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadUser request")
+42
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/hashicorp/watchtower/api"
"github.com/hashicorp/watchtower/api/groups"
"github.com/hashicorp/watchtower/api/hosts"
"github.com/hashicorp/watchtower/api/users"
)
@@ -52,6 +53,47 @@ func (s Organization) UpdateProject(ctx context.Context, r *Project) (*Project,
return target, apiErr, nil
}
func (s Organization) UpdateGroup(ctx context.Context, r *groups.Group) (*groups.Group, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in CreateGroup request")
}
if s.Id == "" {
// Assume the client has been configured with organization already and
// move on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "org", s.Id)
}
id := r.Id
r.Id = ""
req, err := s.Client.NewRequest(ctx, "PATCH", fmt.Sprintf("%s/%s", "groups", id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating CreateGroup request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during UpdateGroup call: %w", err)
}
target := new(groups.Group)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding UpdateGroup repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
func (s Organization) UpdateUser(ctx context.Context, r *users.User) (*users.User, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in CreateUser request")
+1 -1
View File
@@ -27,7 +27,7 @@ func TestUser_Crud(t *testing.T) {
if !assert.Nil(apiErr, step) && apiErr.Message != nil {
t.Errorf("ApiError message: %q", *apiErr.Message)
}
assert.NotNil(u, "returned project", step)
assert.NotNil(u, "returned no resource", step)
gotName := ""
if u.Name != nil {
gotName = *u.Name
+4
View File
@@ -828,6 +828,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8=
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
github.com/jhump/protoreflect v1.6.2-0.20200602154950-9a9113f80b57 h1:DDbkVWVTpzxHbD80e2zpbczB+gkTWI1w2gSeL+viRhc=
github.com/jhump/protoreflect v1.6.2-0.20200602154950-9a9113f80b57/go.mod h1:RZkzh7Hi9J7qT/sPlWnJ/UwZqCJvciFxKDA0UCeltSM=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
@@ -872,8 +873,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
@@ -1075,6 +1078,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.4.0 h1:uCmaf4vVbWAOZz36k1hrQD7ijGRzLwaME8Am/7a4jZI=
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+248
View File
@@ -16,6 +16,171 @@
"application/json"
],
"paths": {
"/v1/orgs/{org_id}/groups": {
"get": {
"summary": "Lists all Groups",
"operationId": "GroupService_ListGroups",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/controller.api.services.v1.ListGroupsResponse"
}
}
},
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "view",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.GroupService"
]
},
"post": {
"summary": "Creates a single Group",
"operationId": "GroupService_CreateGroup",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
],
"tags": [
"controller.api.services.v1.GroupService"
]
}
},
"/v1/orgs/{org_id}/groups/{id}": {
"get": {
"summary": "Gets a single Group",
"operationId": "GroupService_GetGroup",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "view",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.GroupService"
]
},
"delete": {
"summary": "Deletes a Group",
"operationId": "GroupService_DeleteGroup",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/controller.api.services.v1.DeleteGroupResponse"
}
}
},
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"controller.api.services.v1.GroupService"
]
},
"patch": {
"summary": "Updates a Group",
"operationId": "GroupService_UpdateGroup",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"parameters": [
{
"name": "org_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
],
"tags": [
"controller.api.services.v1.GroupService"
]
}
},
"/v1/orgs/{org_id}/host-catalogs": {
"get": {
"summary": "List all Hosts for the specified catalog",
@@ -1737,6 +1902,42 @@
}
},
"definitions": {
"controller.api.resources.groups.v1.Group": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The ID of the Project\nOutput only.",
"readOnly": true
},
"name": {
"type": "string",
"title": "Optional name for identification purposes"
},
"description": {
"type": "string",
"title": "Optional user-set descripton for identification purposes"
},
"created_time": {
"type": "string",
"format": "date-time",
"description": "The time this resource was created\nOutput only.",
"readOnly": true
},
"updated_time": {
"type": "string",
"format": "date-time",
"description": "The time this resource was last updated.\nOutput only.",
"readOnly": true
},
"disabled": {
"type": "boolean",
"format": "boolean",
"title": "Whether the resource is disabled"
}
},
"title": "Group contains all fields related to a Group resource"
},
"controller.api.resources.hosts.v1.Host": {
"type": "object",
"properties": {
@@ -1954,6 +2155,17 @@
"controller.api.services.v1.AddToHostSetResponse": {
"type": "object"
},
"controller.api.services.v1.CreateGroupResponse": {
"type": "object",
"properties": {
"uri": {
"type": "string"
},
"item": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"controller.api.services.v1.CreateHostCatalogResponse": {
"type": "object",
"properties": {
@@ -2009,6 +2221,15 @@
}
}
},
"controller.api.services.v1.DeleteGroupResponse": {
"type": "object",
"properties": {
"existed": {
"type": "boolean",
"format": "boolean"
}
}
},
"controller.api.services.v1.DeleteHostCatalogResponse": {
"type": "object",
"properties": {
@@ -2054,6 +2275,14 @@
}
}
},
"controller.api.services.v1.GetGroupResponse": {
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"controller.api.services.v1.GetHostCatalogResponse": {
"type": "object",
"properties": {
@@ -2094,6 +2323,17 @@
}
}
},
"controller.api.services.v1.ListGroupsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
}
},
"controller.api.services.v1.ListHostCatalogsResponse": {
"type": "object",
"properties": {
@@ -2152,6 +2392,14 @@
"controller.api.services.v1.RemoveFromHostSetResponse": {
"type": "object"
},
"controller.api.services.v1.UpdateGroupResponse": {
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/controller.api.resources.groups.v1.Group"
}
}
},
"controller.api.services.v1.UpdateHostCatalogResponse": {
"type": "object",
"properties": {
@@ -0,0 +1,237 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.24.0
// protoc v3.12.3
// source: controller/api/resources/groups/v1/group.proto
package groups
import (
proto "github.com/golang/protobuf/proto"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
wrappers "github.com/golang/protobuf/ptypes/wrappers"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
// Group contains all fields related to a Group resource
type Group struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The ID of the Project
// Output only.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Optional name for identification purposes
Name *wrappers.StringValue `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
// Optional user-set descripton for identification purposes
Description *wrappers.StringValue `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
// The time this resource was created
// Output only.
CreatedTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=created_time,proto3" json:"created_time,omitempty"`
// The time this resource was last updated.
// Output only.
UpdatedTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=updated_time,proto3" json:"updated_time,omitempty"`
// Whether the resource is disabled
Disabled *wrappers.BoolValue `protobuf:"bytes,6,opt,name=disabled,proto3" json:"disabled,omitempty"`
}
func (x *Group) Reset() {
*x = Group{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_resources_groups_v1_group_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Group) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Group) ProtoMessage() {}
func (x *Group) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_resources_groups_v1_group_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Group.ProtoReflect.Descriptor instead.
func (*Group) Descriptor() ([]byte, []int) {
return file_controller_api_resources_groups_v1_group_proto_rawDescGZIP(), []int{0}
}
func (x *Group) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Group) GetName() *wrappers.StringValue {
if x != nil {
return x.Name
}
return nil
}
func (x *Group) GetDescription() *wrappers.StringValue {
if x != nil {
return x.Description
}
return nil
}
func (x *Group) GetCreatedTime() *timestamp.Timestamp {
if x != nil {
return x.CreatedTime
}
return nil
}
func (x *Group) GetUpdatedTime() *timestamp.Timestamp {
if x != nil {
return x.UpdatedTime
}
return nil
}
func (x *Group) GetDisabled() *wrappers.BoolValue {
if x != nil {
return x.Disabled
}
return nil
}
var File_controller_api_resources_groups_v1_group_proto protoreflect.FileDescriptor
var file_controller_api_resources_groups_v1_group_proto_rawDesc = []byte{
0x0a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x73, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x22, 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, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x02, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x30, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x55, 0x5a, 0x53, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72,
0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 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, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x3b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_api_resources_groups_v1_group_proto_rawDescOnce sync.Once
file_controller_api_resources_groups_v1_group_proto_rawDescData = file_controller_api_resources_groups_v1_group_proto_rawDesc
)
func file_controller_api_resources_groups_v1_group_proto_rawDescGZIP() []byte {
file_controller_api_resources_groups_v1_group_proto_rawDescOnce.Do(func() {
file_controller_api_resources_groups_v1_group_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_groups_v1_group_proto_rawDescData)
})
return file_controller_api_resources_groups_v1_group_proto_rawDescData
}
var file_controller_api_resources_groups_v1_group_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_api_resources_groups_v1_group_proto_goTypes = []interface{}{
(*Group)(nil), // 0: controller.api.resources.groups.v1.Group
(*wrappers.StringValue)(nil), // 1: google.protobuf.StringValue
(*timestamp.Timestamp)(nil), // 2: google.protobuf.Timestamp
(*wrappers.BoolValue)(nil), // 3: google.protobuf.BoolValue
}
var file_controller_api_resources_groups_v1_group_proto_depIdxs = []int32{
1, // 0: controller.api.resources.groups.v1.Group.name:type_name -> google.protobuf.StringValue
1, // 1: controller.api.resources.groups.v1.Group.description:type_name -> google.protobuf.StringValue
2, // 2: controller.api.resources.groups.v1.Group.created_time:type_name -> google.protobuf.Timestamp
2, // 3: controller.api.resources.groups.v1.Group.updated_time:type_name -> google.protobuf.Timestamp
3, // 4: controller.api.resources.groups.v1.Group.disabled:type_name -> google.protobuf.BoolValue
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_controller_api_resources_groups_v1_group_proto_init() }
func file_controller_api_resources_groups_v1_group_proto_init() {
if File_controller_api_resources_groups_v1_group_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_api_resources_groups_v1_group_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Group); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_api_resources_groups_v1_group_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_api_resources_groups_v1_group_proto_goTypes,
DependencyIndexes: file_controller_api_resources_groups_v1_group_proto_depIdxs,
MessageInfos: file_controller_api_resources_groups_v1_group_proto_msgTypes,
}.Build()
File_controller_api_resources_groups_v1_group_proto = out.File
file_controller_api_resources_groups_v1_group_proto_rawDesc = nil
file_controller_api_resources_groups_v1_group_proto_goTypes = nil
file_controller_api_resources_groups_v1_group_proto_depIdxs = nil
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,770 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: controller/api/services/v1/group_service.proto
/*
Package services is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package services
import (
"context"
"io"
"net/http"
"github.com/golang/protobuf/descriptor"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = descriptor.ForMessage
var (
filter_GroupService_GetGroup_0 = &utilities.DoubleArray{Encoding: map[string]int{"org_id": 0, "id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_GroupService_GetGroup_0(ctx context.Context, marshaler runtime.Marshaler, client GroupServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetGroupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_GetGroup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GroupService_GetGroup_0(ctx context.Context, marshaler runtime.Marshaler, server GroupServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetGroupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_GetGroup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetGroup(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GroupService_ListGroups_0 = &utilities.DoubleArray{Encoding: map[string]int{"org_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_GroupService_ListGroups_0(ctx context.Context, marshaler runtime.Marshaler, client GroupServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListGroupsRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_ListGroups_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListGroups(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GroupService_ListGroups_0(ctx context.Context, marshaler runtime.Marshaler, server GroupServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListGroupsRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_ListGroups_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListGroups(ctx, &protoReq)
return msg, metadata, err
}
func request_GroupService_CreateGroup_0(ctx context.Context, marshaler runtime.Marshaler, client GroupServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateGroupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
msg, err := client.CreateGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GroupService_CreateGroup_0(ctx context.Context, marshaler runtime.Marshaler, server GroupServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateGroupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
msg, err := server.CreateGroup(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_GroupService_UpdateGroup_0 = &utilities.DoubleArray{Encoding: map[string]int{"item": 0, "org_id": 1, "id": 2}, Base: []int{1, 1, 2, 3, 0, 0, 0}, Check: []int{0, 1, 1, 1, 2, 3, 4}}
)
func request_GroupService_UpdateGroup_0(ctx context.Context, marshaler runtime.Marshaler, client GroupServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateGroupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 {
_, md := descriptor.ForMessage(protoReq.Item)
if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), md); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
} else {
protoReq.UpdateMask = fieldMask
}
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_UpdateGroup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GroupService_UpdateGroup_0(ctx context.Context, marshaler runtime.Marshaler, server GroupServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateGroupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 {
_, md := descriptor.ForMessage(protoReq.Item)
if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), md); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
} else {
protoReq.UpdateMask = fieldMask
}
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GroupService_UpdateGroup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateGroup(ctx, &protoReq)
return msg, metadata, err
}
func request_GroupService_DeleteGroup_0(ctx context.Context, marshaler runtime.Marshaler, client GroupServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteGroupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.DeleteGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_GroupService_DeleteGroup_0(ctx context.Context, marshaler runtime.Marshaler, server GroupServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteGroupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["org_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "org_id")
}
protoReq.OrgId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err)
}
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.DeleteGroup(ctx, &protoReq)
return msg, metadata, err
}
// RegisterGroupServiceHandlerServer registers the http handlers for service GroupService to "mux".
// UnaryRPC :call GroupServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
func RegisterGroupServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GroupServiceServer) error {
mux.Handle("GET", pattern_GroupService_GetGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GroupService_GetGroup_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_GetGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_GetGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GroupService_ListGroups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GroupService_ListGroups_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_ListGroups_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_GroupService_CreateGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GroupService_CreateGroup_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_CreateGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_CreateGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_GroupService_UpdateGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GroupService_UpdateGroup_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_UpdateGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_UpdateGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_GroupService_DeleteGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_GroupService_DeleteGroup_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_DeleteGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterGroupServiceHandlerFromEndpoint is same as RegisterGroupServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterGroupServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterGroupServiceHandler(ctx, mux, conn)
}
// RegisterGroupServiceHandler registers the http handlers for service GroupService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterGroupServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterGroupServiceHandlerClient(ctx, mux, NewGroupServiceClient(conn))
}
// RegisterGroupServiceHandlerClient registers the http handlers for service GroupService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "GroupServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "GroupServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "GroupServiceClient" to call the correct interceptors.
func RegisterGroupServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client GroupServiceClient) error {
mux.Handle("GET", pattern_GroupService_GetGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GroupService_GetGroup_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_GetGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_GetGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_GroupService_ListGroups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GroupService_ListGroups_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_ListGroups_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_GroupService_CreateGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GroupService_CreateGroup_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_CreateGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_CreateGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_GroupService_UpdateGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GroupService_UpdateGroup_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_UpdateGroup_0(ctx, mux, outboundMarshaler, w, req, response_GroupService_UpdateGroup_0{resp}, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_GroupService_DeleteGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GroupService_DeleteGroup_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GroupService_DeleteGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
type response_GroupService_GetGroup_0 struct {
proto.Message
}
func (m response_GroupService_GetGroup_0) XXX_ResponseBody() interface{} {
response := m.Message.(*GetGroupResponse)
return response.Item
}
type response_GroupService_CreateGroup_0 struct {
proto.Message
}
func (m response_GroupService_CreateGroup_0) XXX_ResponseBody() interface{} {
response := m.Message.(*CreateGroupResponse)
return response.Item
}
type response_GroupService_UpdateGroup_0 struct {
proto.Message
}
func (m response_GroupService_UpdateGroup_0) XXX_ResponseBody() interface{} {
response := m.Message.(*UpdateGroupResponse)
return response.Item
}
var (
pattern_GroupService_GetGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "orgs", "org_id", "groups", "id"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_GroupService_ListGroups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1", "orgs", "org_id", "groups"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_GroupService_CreateGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1", "orgs", "org_id", "groups"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_GroupService_UpdateGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "orgs", "org_id", "groups", "id"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_GroupService_DeleteGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "orgs", "org_id", "groups", "id"}, "", runtime.AssumeColonVerbOpt(true)))
)
var (
forward_GroupService_GetGroup_0 = runtime.ForwardResponseMessage
forward_GroupService_ListGroups_0 = runtime.ForwardResponseMessage
forward_GroupService_CreateGroup_0 = runtime.ForwardResponseMessage
forward_GroupService_UpdateGroup_0 = runtime.ForwardResponseMessage
forward_GroupService_DeleteGroup_0 = runtime.ForwardResponseMessage
)
@@ -0,0 +1,32 @@
syntax = "proto3";
package controller.api.resources.groups.v1;
option go_package = "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/groups;groups";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
// Group contains all fields related to a Group resource
message Group {
// The ID of the Project
// Output only.
string id = 1;
// Optional name for identification purposes
google.protobuf.StringValue name = 2;
// Optional user-set descripton for identification purposes
google.protobuf.StringValue description = 3;
// The time this resource was created
// Output only.
google.protobuf.Timestamp created_time = 4 [json_name="created_time"];
// The time this resource was last updated.
// Output only.
google.protobuf.Timestamp updated_time = 5 [json_name="updated_time"];
// Whether the resource is disabled
google.protobuf.BoolValue disabled = 6;
}
@@ -0,0 +1,138 @@
syntax = "proto3";
package controller.api.services.v1;
option go_package = "github.com/hashicorp/watchtower/internal/gen/controller/api/services;services";
import "protoc-gen-swagger/options/annotations.proto";
import "google/api/annotations.proto";
import "google/protobuf/field_mask.proto";
import "controller/api/resources/groups/v1/group.proto";
service GroupService {
// GetGroup returns a stored Group if present. The provided request
// must include the org and group id for the group being retrieved. If
// any of those ids are missing, malformed or reference a non existing
// resource an error is returned.
rpc GetGroup(GetGroupRequest) returns (GetGroupResponse) {
option (google.api.http) = {
get: "/v1/orgs/{org_id}/groups/{id}"
response_body: "item"
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
summary: "Gets a single Group"
};
}
// ListGroups returns a list of stored groups which exist inside the org
// referenced inside the request. The request must include the org id for
// the groups being retrieved. If the org id is missing, malformed, or
// reference a non existing organization, an error is returned.
rpc ListGroups(ListGroupsRequest) returns (ListGroupsResponse) {
option (google.api.http) = {
get: "/v1/orgs/{org_id}/groups"
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
summary: "Lists all Groups"
};
}
// CreateGroup creates and stores a group in watchtower. The provided
// request must include the org id in which the group will be created.
// If the org id is missing, malformed or references a non existing
// organization, an error is returned. If a name is provided that is in
// use in another group in the same organization, an error is returned.
rpc CreateGroup(CreateGroupRequest) returns (CreateGroupResponse) {
option (google.api.http) = {
post: "/v1/orgs/{org_id}/groups"
body: "item"
response_body: "item"
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
summary: "Creates a single Group"
};
}
// UpdateGroup updates an existing group in watchtower. The provided
// group must not have any read only fields set. The update mask must be
// included in the request and contain at least 1 mutable field. To unset
// a field's value, include the field in the update mask and don't set it
// in the provided group. An error is returned if either the organization
// or group ids are missing or reference a non existing resource. An error
// is also returned if the request attempts to update the name to one that is
// already in use in this organization.
rpc UpdateGroup(UpdateGroupRequest) returns (UpdateGroupResponse) {
option (google.api.http) = {
patch: "/v1/orgs/{org_id}/groups/{id}"
body: "item"
response_body: "item"
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
summary: "Updates a Group"
};
}
// DeleteGroup removes a group from Watchtower. If the provided org or group ids
// are malformed or not provided an error is returned. No error is returned
// if either ids reference resources that do not exist as the response itself
// specifies if the resource existed before the DeleteGroup request was
// received.
rpc DeleteGroup(DeleteGroupRequest) returns (DeleteGroupResponse) {
option (google.api.http) = {
delete: "/v1/orgs/{org_id}/groups/{id}"
};
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
summary: "Deletes a Group"
};
}
}
message GetGroupRequest {
string org_id = 1;
string id = 2;
string view = 3;
}
message GetGroupResponse {
resources.groups.v1.Group item = 1;
}
message ListGroupsRequest {
string org_id = 1;
string view = 2;
}
message ListGroupsResponse {
repeated resources.groups.v1.Group items = 1;
}
message CreateGroupRequest {
string org_id = 1;
resources.groups.v1.Group item = 2;
}
message CreateGroupResponse {
string uri = 1;
resources.groups.v1.Group item = 2;
}
message UpdateGroupRequest {
string org_id = 1;
string id = 2;
resources.groups.v1.Group item = 3;
google.protobuf.FieldMask update_mask = 4;
}
message UpdateGroupResponse {
resources.groups.v1.Group item = 1;
}
message DeleteGroupRequest {
string org_id = 1;
string id = 2;
}
message DeleteGroupResponse {
bool existed = 1;
}
+8
View File
@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/watchtower/globals"
"github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/groups"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_catalogs"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_sets"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/hosts"
@@ -92,6 +93,13 @@ func handleGrpcGateway(c *Controller) (http.Handler, error) {
if err := services.RegisterUserServiceHandlerServer(ctx, mux, us); err != nil {
return nil, fmt.Errorf("failed to register user service handler: %w", err)
}
gs, err := groups.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create group handler service: %w", err)
}
if err := services.RegisterGroupServiceHandlerServer(ctx, mux, gs); err != nil {
return nil, fmt.Errorf("failed to register group service handler: %w", err)
}
return mux, nil
}
@@ -0,0 +1,323 @@
package groups
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/watchtower/internal/db"
pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/groups"
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const orgIdFieldName = "org_id"
var (
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
// TODO(ICU-28): Find a way to auto update these names and enforce the mappings between wire and storage.
wireToStorageMask = map[string]string{
"name": "Name",
"description": "Description",
}
)
// Service handles request as described by the pbs.GroupServiceServer interface.
type Service struct {
repoFn func() (*iam.Repository, error)
}
// NewService returns a group service which handles group related requests to watchtower.
func NewService(repo func() (*iam.Repository, error)) (Service, error) {
if repo == nil {
return Service{}, fmt.Errorf("nil iam repostiroy provided")
}
return Service{repoFn: repo}, nil
}
var _ pbs.GroupServiceServer = Service{}
// CreateGroup is not yet implemented but will implement the interface pbs.GroupServiceServer.
func (s Service) ListGroups(context.Context, *pbs.ListGroupsRequest) (*pbs.ListGroupsResponse, error) {
return nil, status.Error(codes.Unimplemented, "List not enabled for this resource.")
}
// GetGroups implements the interface pbs.GroupServiceServer.
func (s Service) GetGroup(ctx context.Context, req *pbs.GetGroupRequest) (*pbs.GetGroupResponse, error) {
if err := validateGetRequest(req); err != nil {
return nil, err
}
u, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
return &pbs.GetGroupResponse{Item: u}, nil
}
// CreateGroup implements the interface pbs.GroupServiceServer.
func (s Service) CreateGroup(ctx context.Context, req *pbs.CreateGroupRequest) (*pbs.CreateGroupResponse, error) {
if err := validateCreateRequest(req); err != nil {
return nil, err
}
u, err := s.createInRepo(ctx, req.GetOrgId(), req.GetItem())
if err != nil {
return nil, err
}
return &pbs.CreateGroupResponse{Item: u, Uri: fmt.Sprintf("orgs/%s/groups/%s", req.GetOrgId(), u.GetId())}, nil
}
// UpdateGroup implements the interface pbs.GroupServiceServer.
func (s Service) UpdateGroup(ctx context.Context, req *pbs.UpdateGroupRequest) (*pbs.UpdateGroupResponse, error) {
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
u, err := s.updateInRepo(ctx, req.GetOrgId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
return &pbs.UpdateGroupResponse{Item: u}, nil
}
// DeleteGroup implements the interface pbs.GroupServiceServer.
func (s Service) DeleteGroup(ctx context.Context, req *pbs.DeleteGroupRequest) (*pbs.DeleteGroupResponse, error) {
if err := validateDeleteRequest(req); err != nil {
return nil, err
}
existed, err := s.deleteFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
return &pbs.DeleteGroupResponse{Existed: existed}, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*pb.Group, error) {
repo, err := s.repoFn()
if err != nil {
return nil, err
}
u, err := repo.LookupGroup(ctx, id)
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return nil, handlers.NotFoundErrorf("Group %q doesn't exist.", id)
}
return nil, err
}
if u == nil {
return nil, handlers.NotFoundErrorf("Group %q doesn't exist.", id)
}
return toProto(u), nil
}
func (s Service) createInRepo(ctx context.Context, orgId string, item *pb.Group) (*pb.Group, error) {
var opts []iam.Option
if item.GetName() != nil {
opts = append(opts, iam.WithName(item.GetName().GetValue()))
}
if item.GetDescription() != nil {
opts = append(opts, iam.WithDescription(item.GetDescription().GetValue()))
}
u, err := iam.NewGroup(orgId, opts...)
if err != nil {
return nil, status.Errorf(codes.Internal, "Unable to build group for creation: %v.", err)
}
repo, err := s.repoFn()
if err != nil {
return nil, err
}
out, err := repo.CreateGroup(ctx, u)
if err != nil {
return nil, status.Errorf(codes.Internal, "Unable to create group: %v.", err)
}
if out == nil {
return nil, status.Error(codes.Internal, "Unable to create group but no error returned from repository.")
}
return toProto(out), nil
}
func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []string, item *pb.Group) (*pb.Group, error) {
var opts []iam.Option
if desc := item.GetDescription(); desc != nil {
opts = append(opts, iam.WithDescription(desc.GetValue()))
}
if name := item.GetName(); name != nil {
opts = append(opts, iam.WithName(name.GetValue()))
}
u, err := iam.NewGroup(orgId, opts...)
if err != nil {
return nil, status.Errorf(codes.Internal, "Unable to build group for update: %v.", err)
}
u.PublicId = id
dbMask, err := toDbUpdateMask(mask)
if err != nil {
return nil, err
}
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {
return nil, err
}
out, rowsUpdated, err := repo.UpdateGroup(ctx, u, dbMask)
if err != nil {
return nil, status.Errorf(codes.Internal, "Unable to update group: %v.", err)
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Group %q doesn't exist.", id)
}
return toProto(out), nil
}
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
repo, err := s.repoFn()
if err != nil {
return false, err
}
rows, err := repo.DeleteGroup(ctx, id)
if err != nil {
if errors.Is(err, db.ErrRecordNotFound) {
return false, nil
}
return false, status.Errorf(codes.Internal, "Unable to delete group: %v.", err)
}
return rows > 0, nil
}
// toDbUpdateMask converts the wire format's FieldMask into a list of strings containing FieldMask paths used
func toDbUpdateMask(paths []string) ([]string, error) {
var dbPaths []string
var invalid []string
for _, p := range paths {
for _, f := range strings.Split(p, ",") {
if dbField, ok := wireToStorageMask[strings.TrimSpace(f)]; ok {
dbPaths = append(dbPaths, dbField)
} else {
invalid = append(invalid, f)
}
}
}
if len(invalid) > 0 {
return nil, handlers.InvalidArgumentErrorf(fmt.Sprintf("Invalid fields passed in update_update mask: %v.", invalid), map[string]string{"update_mask": fmt.Sprintf("Unknown paths provided in update mask: %q", strings.Join(invalid, ","))})
}
return dbPaths, nil
}
func toProto(in *iam.Group) *pb.Group {
out := pb.Group{
Id: in.GetPublicId(),
CreatedTime: in.GetCreateTime().GetTimestamp(),
UpdatedTime: in.GetUpdateTime().GetTimestamp(),
}
if in.GetDescription() != "" {
out.Description = &wrapperspb.StringValue{Value: in.GetDescription()}
}
if in.GetName() != "" {
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
}
return &out
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// * The path passed in is correctly formatted
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetGroupRequest) error {
badFields := validateAncestors(req)
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["id"] = "Invalid formatted group id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
func validateCreateRequest(req *pbs.CreateGroupRequest) error {
badFields := validateAncestors(req)
item := req.GetItem()
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
}
func validateUpdateRequest(req *pbs.UpdateGroupRequest) error {
badFields := validateAncestors(req)
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["group_id"] = "Improperly formatted path identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update a group."
}
item := req.GetItem()
if item == nil {
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
// the mask will be marked as unset.
return nil
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validateDeleteRequest(req *pbs.DeleteGroupRequest) error {
badFields := validateAncestors(req)
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}
type ancestorProvider interface {
GetOrgId() string
}
// validateAncestors verifies that the ancestors of this call are properly set and provided.
func validateAncestors(r ancestorProvider) map[string]string {
if r.GetOrgId() == "" {
return map[string]string{orgIdFieldName: "Missing organization id."}
}
if !validId(r.GetOrgId(), iam.OrganizationScope.Prefix()+"_") {
return map[string]string{orgIdFieldName: "Improperly formatted identifier."}
}
return map[string]string{}
}
@@ -0,0 +1,533 @@
package groups_test
import (
"context"
"fmt"
"strings"
"testing"
"github.com/golang/protobuf/ptypes"
"github.com/hashicorp/watchtower/internal/db"
pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/groups"
pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services"
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/groups"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createDefaultGroupAndRepo(t *testing.T) (*iam.Group, func() (*iam.Repository, error)) {
t.Helper()
cleanup, conn, _ := db.TestSetup(t, "postgres")
t.Cleanup(func() {
if err := conn.Close(); err != nil {
t.Errorf("Error when closing gorm DB: %v", err)
}
if err := cleanup(); err != nil {
t.Errorf("Error when cleaning up TestSetup: %v", err)
}
})
rw := db.New(conn)
wrap := db.TestWrapper(t)
repoFn := func() (*iam.Repository, error) {
return iam.NewRepository(rw, rw, wrap)
}
o, _ := iam.TestScopes(t, conn)
g := iam.TestGroup(t, conn, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"))
return g, repoFn
}
func TestGet(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
g, repo := createDefaultGroupAndRepo(t)
toMerge := &pbs.GetGroupRequest{
OrgId: g.GetScopeId(),
Id: g.GetPublicId(),
}
wantU := &pb.Group{
Id: g.GetPublicId(),
Name: &wrapperspb.StringValue{Value: g.GetName()},
Description: &wrapperspb.StringValue{Value: g.GetDescription()},
CreatedTime: g.CreateTime.GetTimestamp(),
UpdatedTime: g.UpdateTime.GetTimestamp(),
}
cases := []struct {
name string
req *pbs.GetGroupRequest
res *pbs.GetGroupResponse
errCode codes.Code
}{
{
name: "Get an Existing Group",
req: &pbs.GetGroupRequest{Id: g.GetPublicId()},
res: &pbs.GetGroupResponse{Item: wantU},
errCode: codes.OK,
},
{
name: "Get a non existant Group",
req: &pbs.GetGroupRequest{Id: iam.GroupPrefix + "_DoesntExis"},
res: nil,
errCode: codes.NotFound,
},
{
name: "Wrong id prefix",
req: &pbs.GetGroupRequest{Id: "j_1234567890"},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "space in id",
req: &pbs.GetGroupRequest{Id: iam.GroupPrefix + "_1 23456789"},
res: nil,
errCode: codes.InvalidArgument,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := proto.Clone(toMerge).(*pbs.GetGroupRequest)
proto.Merge(req, tc.req)
s, err := groups.NewService(repo)
require.NoError(err, "Couldn't create new group service.")
got, gErr := s.GetGroup(context.Background(), req)
assert.Equal(tc.errCode, status.Code(gErr), "GetGroup(%+v) got error %v, wanted %v", req, gErr, tc.errCode)
assert.True(proto.Equal(got, tc.res), "GetGroup(%q) got response %q, wanted %q", req, got, tc.res)
})
}
}
func TestDelete(t *testing.T) {
require := require.New(t)
g, repo := createDefaultGroupAndRepo(t)
s, err := groups.NewService(repo)
require.NoError(err, "Error when getting new group service.")
cases := []struct {
name string
req *pbs.DeleteGroupRequest
res *pbs.DeleteGroupResponse
errCode codes.Code
}{
{
name: "Delete an Existing Group",
req: &pbs.DeleteGroupRequest{
OrgId: g.GetScopeId(),
Id: g.GetPublicId(),
},
res: &pbs.DeleteGroupResponse{
Existed: true,
},
errCode: codes.OK,
},
{
name: "Delete bad group id",
req: &pbs.DeleteGroupRequest{
OrgId: g.GetScopeId(),
Id: iam.GroupPrefix + "_doesntexis",
},
res: &pbs.DeleteGroupResponse{
Existed: false,
},
errCode: codes.OK,
},
{
name: "Delete bad org id",
req: &pbs.DeleteGroupRequest{
OrgId: "o_doesntexis",
Id: g.GetPublicId(),
},
res: &pbs.DeleteGroupResponse{
Existed: false,
},
errCode: codes.OK,
},
{
name: "Bad org formatting",
req: &pbs.DeleteGroupRequest{
OrgId: "bad_format",
Id: g.GetPublicId(),
},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "Bad Group Id formatting",
req: &pbs.DeleteGroupRequest{
OrgId: g.GetScopeId(),
Id: "bad_format",
},
res: nil,
errCode: codes.InvalidArgument,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert := assert.New(t)
got, gErr := s.DeleteGroup(context.Background(), tc.req)
assert.Equal(tc.errCode, status.Code(gErr), "DeleteGroup(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode)
assert.EqualValuesf(tc.res, got, "DeleteGroup(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestDelete_twice(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
g, repo := createDefaultGroupAndRepo(t)
s, err := groups.NewService(repo)
require.NoError(err, "Error when getting new group service")
req := &pbs.DeleteGroupRequest{
OrgId: g.GetScopeId(),
Id: g.GetPublicId(),
}
got, gErr := s.DeleteGroup(context.Background(), req)
assert.NoError(gErr, "First attempt")
assert.True(got.GetExisted(), "Expected existed to be true for the first delete.")
got, gErr = s.DeleteGroup(context.Background(), req)
assert.NoError(gErr, "Second attempt")
assert.False(got.GetExisted(), "Expected existed to be false for the second delete.")
}
func TestCreate(t *testing.T) {
require := require.New(t)
defaultGroup, repo := createDefaultGroupAndRepo(t)
defaultCreated, err := ptypes.Timestamp(defaultGroup.GetCreateTime().GetTimestamp())
require.NoError(err, "Error converting proto to timestamp.")
toMerge := &pbs.CreateGroupRequest{
OrgId: defaultGroup.GetScopeId(),
}
cases := []struct {
name string
req *pbs.CreateGroupRequest
res *pbs.CreateGroupResponse
errCode codes.Code
}{
{
name: "Create a valid Group",
req: &pbs.CreateGroupRequest{Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
}},
res: &pbs.CreateGroupResponse{
Uri: fmt.Sprintf("orgs/%s/groups/%s_", defaultGroup.GetScopeId(), iam.GroupPrefix),
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
},
},
errCode: codes.OK,
},
{
name: "Can't specify Id",
req: &pbs.CreateGroupRequest{Item: &pb.Group{
Id: iam.GroupPrefix + "_notallowed",
}},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "Can't specify Created Time",
req: &pbs.CreateGroupRequest{Item: &pb.Group{
CreatedTime: ptypes.TimestampNow(),
}},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "Can't specify Update Time",
req: &pbs.CreateGroupRequest{Item: &pb.Group{
UpdatedTime: ptypes.TimestampNow(),
}},
res: nil,
errCode: codes.InvalidArgument,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert := assert.New(t)
req := proto.Clone(toMerge).(*pbs.CreateGroupRequest)
proto.Merge(req, tc.req)
s, err := groups.NewService(repo)
require.NoError(err, "Error when getting new group service.")
got, gErr := s.CreateGroup(context.Background(), req)
assert.Equal(tc.errCode, status.Code(gErr), "CreateGroup(%+v) got error %v, wanted %v", req, gErr, tc.errCode)
if got != nil {
assert.True(strings.HasPrefix(got.GetUri(), tc.res.Uri))
assert.True(strings.HasPrefix(got.GetItem().GetId(), iam.GroupPrefix+"_"))
gotCreateTime, err := ptypes.Timestamp(got.GetItem().GetCreatedTime())
require.NoError(err, "Error converting proto to timestamp.")
gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime())
require.NoError(err, "Error converting proto to timestamp.")
// Verify it is a group created after the test setup's default group
assert.True(gotCreateTime.After(defaultCreated), "New group should have been created after default group. Was created %v, which is after %v", gotCreateTime, defaultCreated)
assert.True(gotUpdateTime.After(defaultCreated), "New group should have been updated after default group. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
// Clear all values which are hard to compare against.
got.Uri, tc.res.Uri = "", ""
got.Item.Id, tc.res.Item.Id = "", ""
got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil
}
assert.True(proto.Equal(got, tc.res), "CreateGroup(%q) got response %q, wanted %q", req, got, tc.res)
})
}
}
func TestUpdate(t *testing.T) {
require := require.New(t)
g, repoFn := createDefaultGroupAndRepo(t)
tested, err := groups.NewService(repoFn)
require.NoError(err, "Error when getting new group service.")
resetGroup := func() {
repo, err := repoFn()
require.NoError(err, "Couldn't get a new repo")
g, _, err = repo.UpdateGroup(context.Background(), g, []string{"Name", "Description"})
require.NoError(err, "Failed to reset the group")
}
created, err := ptypes.Timestamp(g.GetCreateTime().GetTimestamp())
require.NoError(err, "Error converting proto to timestamp")
toMerge := &pbs.UpdateGroupRequest{
OrgId: g.GetScopeId(),
Id: g.GetPublicId(),
}
cases := []struct {
name string
req *pbs.UpdateGroupRequest
res *pbs.UpdateGroupResponse
errCode codes.Code
}{
{
name: "Update an Existing Group",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name", "description"},
},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
},
},
res: &pbs.UpdateGroupResponse{
Item: &pb.Group{
Id: g.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
CreatedTime: g.GetCreateTime().GetTimestamp(),
},
},
errCode: codes.OK,
},
{
name: "Multiple Paths in single string",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name,description"},
},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
},
},
res: &pbs.UpdateGroupResponse{
Item: &pb.Group{
Id: g.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
CreatedTime: g.GetCreateTime().GetTimestamp(),
},
},
errCode: codes.OK,
},
{
name: "No Update Mask",
req: &pbs.UpdateGroupRequest{
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
errCode: codes.InvalidArgument,
},
{
name: "No Paths in Mask",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{}},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
errCode: codes.InvalidArgument,
},
{
name: "Only non-existant paths in Mask",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistant_field"}},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "updated name"},
Description: &wrapperspb.StringValue{Value: "updated desc"},
},
},
errCode: codes.InvalidArgument,
},
{
name: "Unset Name",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
},
Item: &pb.Group{
Description: &wrapperspb.StringValue{Value: "ignored"},
},
},
res: &pbs.UpdateGroupResponse{
Item: &pb.Group{
Id: g.GetPublicId(),
Description: &wrapperspb.StringValue{Value: "default"},
CreatedTime: g.GetCreateTime().GetTimestamp(),
},
},
errCode: codes.OK,
},
{
name: "Update Only Name",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name"},
},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "updated"},
Description: &wrapperspb.StringValue{Value: "ignored"},
},
},
res: &pbs.UpdateGroupResponse{
Item: &pb.Group{
Id: g.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "updated"},
Description: &wrapperspb.StringValue{Value: "default"},
CreatedTime: g.GetCreateTime().GetTimestamp(),
},
},
errCode: codes.OK,
},
{
name: "Update Only Description",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "ignored"},
Description: &wrapperspb.StringValue{Value: "notignored"},
},
},
res: &pbs.UpdateGroupResponse{
Item: &pb.Group{
Id: g.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "default"},
Description: &wrapperspb.StringValue{Value: "notignored"},
CreatedTime: g.GetCreateTime().GetTimestamp(),
},
},
errCode: codes.OK,
},
// TODO: Updating a non existant group should result in a NotFound exception but currently results in
// the repoFn returning an internal error.
{
name: "Update a Non Existing Group",
req: &pbs.UpdateGroupRequest{
Id: iam.GroupPrefix + "_DoesntExis",
UpdateMask: &field_mask.FieldMask{
Paths: []string{"description"},
},
Item: &pb.Group{
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "desc"},
},
},
errCode: codes.Internal,
},
{
name: "Cant change Id",
req: &pbs.UpdateGroupRequest{
Id: g.GetPublicId(),
UpdateMask: &field_mask.FieldMask{
Paths: []string{"id"},
},
Item: &pb.Group{
Id: iam.GroupPrefix + "_somethinge",
Name: &wrapperspb.StringValue{Value: "new"},
Description: &wrapperspb.StringValue{Value: "new desc"},
}},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "Cant specify Created Time",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"created_time"},
},
Item: &pb.Group{
CreatedTime: ptypes.TimestampNow(),
},
},
res: nil,
errCode: codes.InvalidArgument,
},
{
name: "Cant specify Updated Time",
req: &pbs.UpdateGroupRequest{
UpdateMask: &field_mask.FieldMask{
Paths: []string{"updated_time"},
},
Item: &pb.Group{
UpdatedTime: ptypes.TimestampNow(),
},
},
res: nil,
errCode: codes.InvalidArgument,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
defer resetGroup()
assert := assert.New(t)
req := proto.Clone(toMerge).(*pbs.UpdateGroupRequest)
proto.Merge(req, tc.req)
got, gErr := tested.UpdateGroup(context.Background(), req)
assert.Equal(tc.errCode, status.Code(gErr), "UpdateGroup(%+v) got error %v, wanted %v", req, gErr, tc.errCode)
if got != nil {
assert.NotNilf(tc.res, "Expected UpdateGroup response to be nil, but was %v", got)
gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime())
require.NoError(err, "Error converting proto to timestamp")
// Verify it is a group updated after it was created
assert.True(gotUpdateTime.After(created), "Updated group should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
// Clear all values which are hard to compare against.
got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil
}
assert.True(proto.Equal(got, tc.res), "UpdateGroup(%q) got response %q, wanted %q", req, got, tc.res)
})
}
}