Adding SDK ability to do CRUD operations on groups and roles under the project scope. (#157)

This commit is contained in:
Todd Knight authored and GitHub committed 2020-06-30 10:25:58 -07:00
1 parent 8da4effd5e
commit 9a1539867e
11 files changed
+554 -78

No files matched your search

+93 -38
View File
@@ -1,6 +1,7 @@
package groups_test
import (
"context"
"net/http"
"testing"
@@ -10,8 +11,16 @@ import (
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type groupCrud interface {
CreateGroup(context.Context, *groups.Group) (*groups.Group, *api.Error, error)
ReadGroup(context.Context, *groups.Group) (*groups.Group, *api.Error, error)
UpdateGroup(context.Context, *groups.Group) (*groups.Group, *api.Error, error)
DeleteGroup(context.Context, *groups.Group) (bool, *api.Error, error)
}
func TestGroup_Crud(t *testing.T) {
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
@@ -21,6 +30,10 @@ func TestGroup_Crud(t *testing.T) {
Client: client,
}
proj, apiErr, err := org.CreateProject(tc.Context(), &scopes.Project{})
require.NoError(t, err)
require.Nil(t, apiErr)
checkGroup := func(step string, g *groups.Group, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
@@ -35,29 +48,49 @@ func TestGroup_Crud(t *testing.T) {
assert.Equal(wantedName, gotName, step)
}
g, apiErr, err := org.CreateGroup(tc.Context(), &groups.Group{Name: api.String("foo")})
checkGroup("create", g, apiErr, err, "foo")
cases := []struct {
name string
scope groupCrud
}{
{
name: "org",
scope: org,
},
{
name: "project",
scope: proj,
},
}
g, apiErr, err = org.ReadGroup(tc.Context(), &groups.Group{Id: g.Id})
checkGroup("read", g, apiErr, err, "foo")
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
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, apiErr, err := tt.scope.CreateGroup(tc.Context(), &groups.Group{Name: api.String("foo")})
checkGroup("create", g, apiErr, err, "foo")
g = &groups.Group{Id: g.Id}
g.SetDefault("name")
g, apiErr, err = org.UpdateGroup(tc.Context(), g)
checkGroup("update", g, apiErr, err, "")
g, apiErr, err = tt.scope.ReadGroup(tc.Context(), &groups.Group{Id: g.Id})
checkGroup("read", g, apiErr, err, "foo")
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.")
g = &groups.Group{Id: g.Id}
g.Name = api.String("bar")
g, apiErr, err = tt.scope.UpdateGroup(tc.Context(), g)
checkGroup("update", g, apiErr, err, "bar")
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.")
g = &groups.Group{Id: g.Id}
g.SetDefault("name")
g, apiErr, err = tt.scope.UpdateGroup(tc.Context(), g)
checkGroup("update", g, apiErr, err, "")
existed, apiErr, err := tt.scope.DeleteGroup(tc.Context(), g)
assert.NoError(t, err)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
existed, apiErr, err = tt.scope.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) {
@@ -71,28 +104,50 @@ func TestGroup_Errors(t *testing.T) {
Client: client,
}
u, apiErr, err := org.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
proj, apiErr, err := org.CreateProject(tc.Context(), &scopes.Project{})
require.NoError(t, err)
require.Nil(t, apiErr)
// Create another resource with the same name.
_, apiErr, err = org.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.NotNil(apiErr)
cases := []struct {
name string
scope groupCrud
}{
{
name: "org",
scope: org,
},
{
name: "project",
scope: proj,
},
}
_, apiErr, err = org.ReadGroup(ctx, &groups.Group{Id: iam.GroupPrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
u, apiErr, err := tt.scope.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
_, apiErr, err = org.ReadGroup(ctx, &groups.Group{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
// Create another resource with the same name.
_, apiErr, err = tt.scope.CreateGroup(ctx, &groups.Group{Name: api.String("first")})
assert.NoError(err)
assert.NotNil(apiErr)
_, apiErr, err = org.UpdateGroup(ctx, &groups.Group{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
_, apiErr, err = tt.scope.ReadGroup(ctx, &groups.Group{Id: iam.GroupPrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
_, apiErr, err = tt.scope.ReadGroup(ctx, &groups.Group{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
_, apiErr, err = tt.scope.UpdateGroup(ctx, &groups.Group{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
})
}
}
+10
View File
@@ -54,6 +54,16 @@ var createFuncs = map[string][]*createInfo{
targetType: "hosts.HostCatalog",
path: `"host-catalogs"`,
},
{
baseType: "Project",
targetType: "groups.Group",
path: `"groups"`,
},
{
baseType: "Project",
targetType: "roles.Role",
path: `"roles"`,
},
},
}
+10
View File
@@ -42,6 +42,16 @@ var deleteFuncs = map[string][]*deleteInfo{
"hosts.HostCatalog",
"host-catalogs",
},
{
"Project",
"groups.Group",
"groups",
},
{
"Project",
"roles.Role",
"roles",
},
},
}
+10
View File
@@ -42,6 +42,16 @@ var readFuncs = map[string][]*readInfo{
targetType: "hosts.HostCatalog",
path: "host-catalogs",
},
{
baseType: "Project",
targetType: "groups.Group",
path: "groups",
},
{
baseType: "Project",
targetType: "roles.Role",
path: "roles",
},
},
}
+10
View File
@@ -42,6 +42,16 @@ var updateFuncs = map[string][]*updateInfo{
targetType: "hosts.HostCatalog",
path: "host-catalogs",
},
{
baseType: "Project",
targetType: "groups.Group",
path: "groups",
},
{
baseType: "Project",
targetType: "roles.Role",
path: "roles",
},
},
}
+91 -38
View File
@@ -1,6 +1,7 @@
package roles_test
import (
"context"
"net/http"
"testing"
@@ -10,8 +11,16 @@ import (
"github.com/hashicorp/watchtower/internal/iam"
"github.com/hashicorp/watchtower/internal/servers/controller"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type roleCrud interface {
CreateRole(context.Context, *roles.Role) (*roles.Role, *api.Error, error)
ReadRole(context.Context, *roles.Role) (*roles.Role, *api.Error, error)
UpdateRole(context.Context, *roles.Role) (*roles.Role, *api.Error, error)
DeleteRole(context.Context, *roles.Role) (bool, *api.Error, error)
}
func TestRole_Crud(t *testing.T) {
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
@@ -21,6 +30,10 @@ func TestRole_Crud(t *testing.T) {
Client: client,
}
proj, apiErr, err := org.CreateProject(tc.Context(), &scopes.Project{})
require.NoError(t, err)
require.Nil(t, apiErr)
checkRole := func(step string, g *roles.Role, apiErr *api.Error, err error, wantedName string) {
assert := assert.New(t)
assert.NoError(err, step)
@@ -35,29 +48,47 @@ func TestRole_Crud(t *testing.T) {
assert.Equal(wantedName, gotName, step)
}
g, apiErr, err := org.CreateRole(tc.Context(), &roles.Role{Name: api.String("foo")})
checkRole("create", g, apiErr, err, "foo")
cases := []struct {
name string
scope roleCrud
}{
{
name: "org",
scope: org,
},
{
name: "project",
scope: proj,
},
}
g, apiErr, err = org.ReadRole(tc.Context(), &roles.Role{Id: g.Id})
checkRole("read", g, apiErr, err, "foo")
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
g, apiErr, err := tt.scope.CreateRole(tc.Context(), &roles.Role{Name: api.String("foo")})
checkRole("create", g, apiErr, err, "foo")
g = &roles.Role{Id: g.Id}
g.Name = api.String("bar")
g, apiErr, err = org.UpdateRole(tc.Context(), g)
checkRole("update", g, apiErr, err, "bar")
g, apiErr, err = tt.scope.ReadRole(tc.Context(), &roles.Role{Id: g.Id})
checkRole("read", g, apiErr, err, "foo")
g = &roles.Role{Id: g.Id}
g.SetDefault("name")
g, apiErr, err = org.UpdateRole(tc.Context(), g)
checkRole("update", g, apiErr, err, "")
g = &roles.Role{Id: g.Id}
g.Name = api.String("bar")
g, apiErr, err = tt.scope.UpdateRole(tc.Context(), g)
checkRole("update", g, apiErr, err, "bar")
existed, apiErr, err := org.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
g = &roles.Role{Id: g.Id}
g.SetDefault("name")
g, apiErr, err = tt.scope.UpdateRole(tc.Context(), g)
checkRole("update", g, apiErr, err, "")
existed, apiErr, err = org.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.False(t, existed, "Expected user to not exist when deleted, but it did.")
existed, apiErr, err := tt.scope.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.True(t, existed, "Expected existing user when deleted, but it wasn't.")
existed, apiErr, err = tt.scope.DeleteRole(tc.Context(), g)
assert.NoError(t, err)
assert.False(t, existed, "Expected user to not exist when deleted, but it did.")
})
}
}
func TestRole_Errors(t *testing.T) {
@@ -71,28 +102,50 @@ func TestRole_Errors(t *testing.T) {
Client: client,
}
u, apiErr, err := org.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
proj, apiErr, err := org.CreateProject(tc.Context(), &scopes.Project{})
require.NoError(t, err)
require.Nil(t, apiErr)
// Create another resource with the same name.
_, apiErr, err = org.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
assert.NotNil(apiErr)
cases := []struct {
name string
scope roleCrud
}{
{
name: "org",
scope: org,
},
{
name: "project",
scope: proj,
},
}
_, apiErr, err = org.ReadRole(ctx, &roles.Role{Id: iam.RolePrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
u, apiErr, err := tt.scope.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
assert.Nil(apiErr)
assert.NotNil(u)
_, apiErr, err = org.ReadRole(ctx, &roles.Role{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
// Create another resource with the same name.
_, apiErr, err = tt.scope.CreateRole(ctx, &roles.Role{Name: api.String("first")})
assert.NoError(err)
assert.NotNil(apiErr)
_, apiErr, err = org.UpdateRole(ctx, &roles.Role{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
_, apiErr, err = tt.scope.ReadRole(ctx, &roles.Role{Id: iam.RolePrefix + "_doesntexis"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusNotFound)
_, apiErr, err = tt.scope.ReadRole(ctx, &roles.Role{Id: "invalid id"})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
_, apiErr, err = tt.scope.UpdateRole(ctx, &roles.Role{Id: u.Id})
assert.NoError(err)
assert.NotNil(apiErr)
assert.EqualValues(*apiErr.Status, http.StatusBadRequest)
})
}
}
+76
View File
@@ -202,3 +202,79 @@ func (s Project) CreateHostCatalog(ctx context.Context, r *hosts.HostCatalog) (*
return target, apiErr, nil
}
func (s Project) 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 project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", 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 Project) CreateRole(ctx context.Context, r *roles.Role) (*roles.Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in Createroles.Role request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
req, err := s.Client.NewRequest(ctx, "POST", "roles", r)
if err != nil {
return nil, nil, fmt.Errorf("error creating Createroles.Role request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during Createroles.Role call: %w", err)
}
target := new(roles.Role)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding Createroles.Role repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
+88
View File
@@ -231,3 +231,91 @@ func (s Project) DeleteHostCatalog(ctx context.Context, r *hosts.HostCatalog) (b
return target.Existed, apiErr, nil
}
// DeleteGroup returns true iff the groups.Group existed when the delete attempt was made.
func (s Project) 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 project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", 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
}
// DeleteRole returns true iff the roles.Role existed when the delete attempt was made.
func (s Project) DeleteRole(ctx context.Context, r *roles.Role) (bool, *api.Error, error) {
if s.Client == nil {
return false, nil, fmt.Errorf("nil client in DeleteRole request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
if r.Id == "" {
return false, nil, fmt.Errorf("empty roles.Role ID field in DeleteRole request")
}
req, err := s.Client.NewRequest(ctx, "DELETE", fmt.Sprintf("%s/%s", "roles", r.Id), nil)
if err != nil {
return false, nil, fmt.Errorf("error creating DeleteRole request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return false, nil, fmt.Errorf("error performing client request during DeleteRole 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 DeleteRole repsonse: %w", err)
}
return target.Existed, apiErr, nil
}
+82
View File
@@ -217,3 +217,85 @@ func (s Project) ReadHostCatalog(ctx context.Context, r *hosts.HostCatalog) (*ho
return target, apiErr, nil
}
func (s Project) 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 project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", 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 Project) ReadRole(ctx context.Context, r *roles.Role) (*roles.Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in ReadRole request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
if r.Id == "" {
return nil, nil, fmt.Errorf("empty roles.Role ID field in ReadRole request")
}
req, err := s.Client.NewRequest(ctx, "GET", fmt.Sprintf("%s/%s", "roles", r.Id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating ReadRole request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during ReadRole call: %w", err)
}
target := new(roles.Role)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding ReadRole repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
+82
View File
@@ -217,3 +217,85 @@ func (s Project) UpdateHostCatalog(ctx context.Context, r *hosts.HostCatalog) (*
return target, apiErr, nil
}
func (s Project) 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 project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", 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 Project) UpdateRole(ctx context.Context, r *roles.Role) (*roles.Role, *api.Error, error) {
if s.Client == nil {
return nil, nil, fmt.Errorf("nil client in CreateRole request")
}
if s.Id == "" {
// Assume the client has been configured with project already and move
// on
} else {
// If it's explicitly set here, override anything that might be in the
// client
ctx = context.WithValue(ctx, "project", s.Id)
}
id := r.Id
r.Id = ""
req, err := s.Client.NewRequest(ctx, "PATCH", fmt.Sprintf("%s/%s", "roles", id), r)
if err != nil {
return nil, nil, fmt.Errorf("error creating CreateRole request: %w", err)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("error performing client request during UpdateRole call: %w", err)
}
target := new(roles.Role)
apiErr, err := resp.Decode(target)
if err != nil {
return nil, nil, fmt.Errorf("error decoding UpdateRole repsonse: %w", err)
}
target.Client = s.Client
return target, apiErr, nil
}
+2 -2
View File
@@ -9,6 +9,6 @@ import (
type Config struct {
*base.Server
RawConfig *config.Config
BaseContext context.Context
RawConfig *config.Config
BaseContext context.Context
}