Skip to content

Commit

Permalink
Delete a user from an organization
Browse files Browse the repository at this point in the history
  • Loading branch information
dadgar committed Feb 6, 2024
1 parent 7950ca9 commit 611e6c7
Show file tree
Hide file tree
Showing 27 changed files with 264 additions and 24 deletions.
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestNewCmdCreate(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdDelete(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
Error: "Organization ID must be configured",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/members/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdAdd(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/members/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdDelete(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/members/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/groups/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewCmdRead(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/roles/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
Error: "Organization ID must be configured",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/serviceprincipals/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestNewCmdCreate(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/serviceprincipals/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewCmdDelete(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestNewCmdCreate(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewCmdDelete(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/serviceprincipals/keys/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
Error: "Organization ID must be configured",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/serviceprincipals/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
Error: "Organization ID must be configured",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/serviceprincipals/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestNewCmdRead(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
92 changes: 92 additions & 0 deletions internal/commands/iam/users/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package users

import (
"context"
"fmt"

"github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/client/iam_service"
"github.com/hashicorp/hcp/internal/pkg/cmd"
"github.com/hashicorp/hcp/internal/pkg/heredoc"
"github.com/hashicorp/hcp/internal/pkg/iostreams"
"github.com/hashicorp/hcp/internal/pkg/profile"
)

func NewCmdDelete(ctx *cmd.Context, runF func(*DeleteOpts) error) *cmd.Command {
opts := &DeleteOpts{
Ctx: ctx.ShutdownCtx,
Profile: ctx.Profile,
IO: ctx.IO,
Client: iam_service.New(ctx.HCP, nil),
}

cmd := &cmd.Command{
Name: "delete",
ShortHelp: "Delete a user from the organization.",
LongHelp: heredoc.New(ctx.IO).Must(`
The {{ Bold "hcp iam users delete" }} command deletes a user from the organization.
`),
Examples: []cmd.Example{
{
Preamble: `Delete a user`,
Command: heredoc.New(ctx.IO, heredoc.WithPreserveNewlines()).Must(`
$ hcp iam users delete example-id-123
`),
},
},
Args: cmd.PositionalArguments{
Args: []cmd.PositionalArgument{
{
Name: "ID",
Documentation: "The ID of the user to delete.",
},
},
},
RunF: func(c *cmd.Command, args []string) error {
opts.ID = args[0]
if runF != nil {
return runF(opts)
}
return deleteRun(opts)
},
PersistentPreRun: func(c *cmd.Command, args []string) error {
return cmd.RequireOrganization(ctx)
},
}

return cmd
}

type DeleteOpts struct {
Ctx context.Context
IO iostreams.IOStreams
Profile *profile.Profile

ID string
Client iam_service.ClientService
}

func deleteRun(opts *DeleteOpts) error {
if opts.IO.CanPrompt() {
ok, err := opts.IO.PromptConfirm("The user will be deleted from the organization.\n\nDo you want to continue")
if err != nil {
return fmt.Errorf("failed to retrieve confirmation: %w", err)
}

if !ok {
return nil
}
}

req := iam_service.NewIamServiceDeleteOrganizationMembershipParamsWithContext(opts.Ctx)
req.OrganizationID = opts.Profile.OrganizationID
req.UserPrincipalID = opts.ID

_, err := opts.Client.IamServiceDeleteOrganizationMembership(req, nil)
if err != nil {
return fmt.Errorf("failed to delete user principal from organization: %w", err)
}

fmt.Fprintf(opts.IO.Err(), "%s User %q deleted from organization\n",
opts.IO.ColorScheme().SuccessIcon(), opts.ID)
return nil
}
147 changes: 147 additions & 0 deletions internal/commands/iam/users/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package users

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/iam_service"
mock_iam_service "github.com/hashicorp/hcp/internal/pkg/api/mocks/github.com/hashicorp/hcp-sdk-go/clients/cloud-iam/stable/2019-12-10/client/iam_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 TestNewCmdDelete(t *testing.T) {
t.Parallel()

cases := []struct {
Name string
Args []string
Profile func(t *testing.T) *profile.Profile
Error string
ExpectID string
}{
{
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").SetProjectID("456")
},
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").SetProjectID("456")
},
Args: []string{"foo"},
ExpectID: "foo",
},
}

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 *DeleteOpts
readCmd := NewCmdDelete(ctx, func(o *DeleteOpts) error {
gotOpts = o
return nil
})
readCmd.SetIO(io)

code := readCmd.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.ExpectID, gotOpts.ID)
})
}
}

func TestDeleteRun(t *testing.T) {
t.Parallel()

cases := []struct {
Name string
RespErr bool
Error string
}{
{
Name: "Server error",
RespErr: true,
Error: "failed to delete user principal from organization: [DELETE /iam/2019-12-10/organizations/{organization_id}/user-principals/{user_principal_id}][403]",
},
{
Name: "Good",
},
}

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_iam_service.NewMockClientService(t)
opts := &DeleteOpts{
Ctx: context.Background(),
Profile: profile.TestProfile(t).SetOrgID("123"),
IO: io,
Client: iam,
ID: "456",
}

// Expect a request to get the user.
call := iam.EXPECT().IamServiceDeleteOrganizationMembership(mock.MatchedBy(func(req *iam_service.IamServiceDeleteOrganizationMembershipParams) bool {
return req.OrganizationID == "123" && req.UserPrincipalID == "456"
}), nil).Once()

if c.RespErr {
call.Return(nil, iam_service.NewIamServiceDeleteOrganizationMembershipDefault(http.StatusForbidden))
} else {
ok := iam_service.NewIamServiceDeleteOrganizationMembershipOK()
call.Return(ok, nil)
}

// Run the command
err := deleteRun(opts)
if c.Error != "" {
r.ErrorContains(err, c.Error)
return
}

r.NoError(err)
r.Contains(io.Error.String(), "User \"456\" deleted from organization")
})
}
}
2 changes: 1 addition & 1 deletion internal/commands/iam/users/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestNewCmdList(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured",
Error: "Organization ID must be configured",
},
{
Name: "Too many args",
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/iam/users/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewCmdRead(t *testing.T) {
Name: "No Org",
Profile: profile.TestProfile,
Args: []string{},
Error: "Organization ID must be configured before running the command.",
Error: "Organization ID must be configured before running the command.",
},
{
Name: "Too many args",
Expand Down
Loading

0 comments on commit 611e6c7

Please sign in to comment.