-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from hashicorp/f-group-updates
Add update group command
- Loading branch information
Showing
9 changed files
with
423 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
iam groups: Add update group command. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package groups | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/client/groups_service" | ||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/models" | ||
"github.com/hashicorp/hcp/internal/commands/iam/groups/helper" | ||
"github.com/hashicorp/hcp/internal/pkg/cmd" | ||
"github.com/hashicorp/hcp/internal/pkg/flagvalue" | ||
"github.com/hashicorp/hcp/internal/pkg/heredoc" | ||
"github.com/hashicorp/hcp/internal/pkg/iostreams" | ||
"github.com/hashicorp/hcp/internal/pkg/profile" | ||
) | ||
|
||
func NewCmdUpdate(ctx *cmd.Context, runF func(*UpdateOpts) error) *cmd.Command { | ||
opts := &UpdateOpts{ | ||
Ctx: ctx.ShutdownCtx, | ||
Profile: ctx.Profile, | ||
IO: ctx.IO, | ||
Client: groups_service.New(ctx.HCP, nil), | ||
} | ||
|
||
cmd := &cmd.Command{ | ||
Name: "update", | ||
ShortHelp: "Update an existing group.", | ||
LongHelp: heredoc.New(ctx.IO).Must(` | ||
The {{ template "mdCodeOrBold" "hcp iam groups update" }} command updates a group. | ||
Update can be used to update the display name or description of an existing group. | ||
`), | ||
Examples: []cmd.Example{ | ||
{ | ||
Preamble: "Update a group's description:", | ||
Command: heredoc.New(ctx.IO, heredoc.WithPreserveNewlines()).Must(` | ||
$ hcp iam groups update example-group \ | ||
--description="updated description" \ | ||
--display-name="new display name" | ||
`), | ||
}, | ||
}, | ||
Args: cmd.PositionalArguments{ | ||
Args: []cmd.PositionalArgument{ | ||
{ | ||
Name: "GROUP_NAME", | ||
Documentation: heredoc.New(ctx.IO).Mustf(helper.GroupNameArgDoc, "update"), | ||
}, | ||
}, | ||
Autocomplete: helper.PredictGroupResourceNameSuffix(opts.Ctx, opts.Profile.OrganizationID, opts.Client), | ||
}, | ||
Flags: cmd.Flags{ | ||
Local: []*cmd.Flag{ | ||
{ | ||
Name: "description", | ||
DisplayValue: "NEW_DESCRIPTION", | ||
Description: "New description for the group.", | ||
Value: flagvalue.Simple((*string)(nil), &opts.Description), | ||
}, | ||
{ | ||
Name: "display-name", | ||
DisplayValue: "NEW_DISPLAY_NAME", | ||
Description: "New display name for the group.", | ||
Value: flagvalue.Simple((*string)(nil), &opts.DisplayName), | ||
}, | ||
}, | ||
}, | ||
RunF: func(c *cmd.Command, args []string) error { | ||
opts.Name = args[0] | ||
if runF != nil { | ||
return runF(opts) | ||
} | ||
|
||
return updateRun(opts) | ||
}, | ||
PersistentPreRun: func(c *cmd.Command, args []string) error { | ||
return cmd.RequireOrganization(ctx) | ||
}, | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
type UpdateOpts struct { | ||
Ctx context.Context | ||
Profile *profile.Profile | ||
IO iostreams.IOStreams | ||
|
||
Name string | ||
DisplayName *string | ||
Description *string | ||
Client groups_service.ClientService | ||
} | ||
|
||
func updateRun(opts *UpdateOpts) error { | ||
if opts.DisplayName == nil && opts.Description == nil { | ||
return fmt.Errorf("either display name or description must be specified") | ||
} | ||
|
||
rn := helper.ResourceName(opts.Name, opts.Profile.OrganizationID) | ||
req := groups_service.NewGroupsServiceUpdateGroup2ParamsWithContext(opts.Ctx) | ||
req.ResourceName = rn | ||
req.Group = &models.HashicorpCloudIamGroup{} | ||
|
||
if opts.DisplayName != nil { | ||
req.Group.DisplayName = *opts.DisplayName | ||
} | ||
if opts.Description != nil { | ||
req.Group.Description = *opts.Description | ||
} | ||
|
||
if _, err := opts.Client.GroupsServiceUpdateGroup2(req, nil); err != nil { | ||
return fmt.Errorf("failed to update group: %w", err) | ||
} | ||
|
||
fmt.Fprintf(opts.IO.Err(), "%s Group %q updated\n", | ||
opts.IO.ColorScheme().SuccessIcon(), rn) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package groups | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/go-openapi/runtime/client" | ||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/client/groups_service" | ||
"github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/models" | ||
mock_groups_service "github.com/hashicorp/hcp/internal/pkg/api/mocks/github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/client/groups_service" | ||
"github.com/hashicorp/hcp/internal/pkg/cmd" | ||
"github.com/hashicorp/hcp/internal/pkg/format" | ||
"github.com/hashicorp/hcp/internal/pkg/iostreams" | ||
"github.com/hashicorp/hcp/internal/pkg/profile" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNewCmdUpdate(t *testing.T) { | ||
t.Parallel() | ||
|
||
bar, baz := "bar", "baz" | ||
cases := []struct { | ||
Name string | ||
Args []string | ||
Profile func(t *testing.T) *profile.Profile | ||
Error string | ||
Expect *UpdateOpts | ||
}{ | ||
{ | ||
Name: "No Org", | ||
Profile: profile.TestProfile, | ||
Args: []string{}, | ||
Error: "Organization ID must be configured before running the command.", | ||
}, | ||
{ | ||
Name: "Too many args", | ||
Profile: func(t *testing.T) *profile.Profile { | ||
return profile.TestProfile(t).SetOrgID("123") | ||
}, | ||
Args: []string{"foo", "bar"}, | ||
Error: "accepts 1 arg(s), received 2", | ||
}, | ||
{ | ||
Name: "Good", | ||
Profile: func(t *testing.T) *profile.Profile { | ||
return profile.TestProfile(t).SetOrgID("123") | ||
}, | ||
Args: []string{"foo", "--display-name", "bar", "--description", "baz"}, | ||
Expect: &UpdateOpts{ | ||
Name: "foo", | ||
DisplayName: &bar, | ||
Description: &baz, | ||
}, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
c := c | ||
t.Run(c.Name, func(t *testing.T) { | ||
t.Parallel() | ||
r := require.New(t) | ||
|
||
// Create a context. | ||
io := iostreams.Test() | ||
ctx := &cmd.Context{ | ||
IO: io, | ||
Profile: c.Profile(t), | ||
Output: format.New(io), | ||
HCP: &client.Runtime{}, | ||
ShutdownCtx: context.Background(), | ||
} | ||
|
||
var gotOpts *UpdateOpts | ||
createCmd := NewCmdUpdate(ctx, func(o *UpdateOpts) error { | ||
gotOpts = o | ||
return nil | ||
}) | ||
createCmd.SetIO(io) | ||
|
||
code := createCmd.Run(c.Args) | ||
if c.Error != "" { | ||
r.NotZero(code) | ||
r.Contains(io.Error.String(), c.Error) | ||
return | ||
} | ||
|
||
r.Zero(code, io.Error.String()) | ||
r.NotNil(gotOpts) | ||
r.Equal(c.Expect.Name, gotOpts.Name) | ||
r.Equal(c.Expect.DisplayName, gotOpts.DisplayName) | ||
r.EqualValues(c.Expect.Description, gotOpts.Description) | ||
}) | ||
} | ||
} | ||
|
||
func TestCreateUpdateNoFields(t *testing.T) { | ||
t.Parallel() | ||
r := require.New(t) | ||
|
||
io := iostreams.Test() | ||
iam := mock_groups_service.NewMockClientService(t) | ||
opts := &UpdateOpts{ | ||
Ctx: context.Background(), | ||
IO: io, | ||
Profile: profile.TestProfile(t).SetOrgID("123"), | ||
Client: iam, | ||
Name: "test", | ||
} | ||
|
||
err := updateRun(opts) | ||
r.ErrorContains(err, "either display name or description must be specified") | ||
|
||
} | ||
|
||
func TestCreateUpdate(t *testing.T) { | ||
t.Parallel() | ||
|
||
cases := []struct { | ||
Name string | ||
RespErr bool | ||
GivenName string | ||
ExpectedName string | ||
DisplayName string | ||
Description string | ||
Error string | ||
}{ | ||
{ | ||
Name: "Server error", | ||
GivenName: "test-group", | ||
ExpectedName: "iam/organization/123/group/test-group", | ||
Description: "This is a test", | ||
RespErr: true, | ||
Error: "failed to update group: [PATCH /iam/2019-12-10/{resource_name}][403]", | ||
}, | ||
{ | ||
Name: "Good suffix", | ||
GivenName: "test-group", | ||
ExpectedName: "iam/organization/123/group/test-group", | ||
DisplayName: "new display name", | ||
Description: "This is a test", | ||
}, | ||
{ | ||
Name: "Good resource name", | ||
GivenName: "iam/organization/456/group/test-group", | ||
ExpectedName: "iam/organization/456/group/test-group", | ||
DisplayName: "new display name", | ||
Description: "This is a test", | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
c := c | ||
t.Run(c.Name, func(t *testing.T) { | ||
t.Parallel() | ||
r := require.New(t) | ||
|
||
io := iostreams.Test() | ||
iam := mock_groups_service.NewMockClientService(t) | ||
opts := &UpdateOpts{ | ||
Ctx: context.Background(), | ||
IO: io, | ||
Profile: profile.TestProfile(t).SetOrgID("123"), | ||
Client: iam, | ||
Name: c.GivenName, | ||
} | ||
if c.DisplayName != "" { | ||
opts.DisplayName = &c.DisplayName | ||
} | ||
if c.Description != "" { | ||
opts.Description = &c.Description | ||
} | ||
|
||
// Expect a request to get the user. | ||
call := iam.EXPECT().GroupsServiceUpdateGroup2(mock.MatchedBy(func(req *groups_service.GroupsServiceUpdateGroup2Params) bool { | ||
if req.ResourceName != c.ExpectedName { | ||
return false | ||
} | ||
|
||
if c.DisplayName != "" && req.Group.DisplayName != c.DisplayName { | ||
return false | ||
} | ||
|
||
if c.Description != "" && req.Group.Description != c.Description { | ||
return false | ||
} | ||
|
||
return true | ||
}), nil).Once() | ||
|
||
if c.RespErr { | ||
call.Return(nil, groups_service.NewGroupsServiceUpdateGroup2Default(http.StatusForbidden)) | ||
} else { | ||
ok := groups_service.NewGroupsServiceUpdateGroup2OK() | ||
ok.Payload = &models.HashicorpCloudIamCreateGroupResponse{ | ||
Group: &models.HashicorpCloudIamGroup{ | ||
ResourceID: "iam.group:123456", | ||
ResourceName: c.ExpectedName, | ||
}, | ||
} | ||
|
||
call.Return(ok, nil) | ||
} | ||
|
||
// Run the command | ||
err := updateRun(opts) | ||
if c.Error != "" { | ||
r.ErrorContains(err, c.Error) | ||
return | ||
} | ||
|
||
r.NoError(err) | ||
r.Contains(io.Error.String(), c.ExpectedName) | ||
}) | ||
} | ||
} |
Oops, something went wrong.