mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: implement cli list organization members (#13555)
example cli command: `coder organization members`
This commit is contained in:
@@ -205,6 +205,24 @@ func renderTable(out any, sort string, headers table.Row, filterColumns []string
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against nil dereferences
|
||||
if v != nil {
|
||||
rt := reflect.TypeOf(v)
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
// By default, the behavior is '%v', which just returns a string like
|
||||
// '[a b c]'. This will add commas in between each value.
|
||||
strs := make([]string, 0)
|
||||
vt := reflect.ValueOf(v)
|
||||
for i := 0; i < vt.Len(); i++ {
|
||||
strs = append(strs, fmt.Sprintf("%v", vt.Index(i).Interface()))
|
||||
}
|
||||
v = "[" + strings.Join(strs, ", ") + "]"
|
||||
default:
|
||||
// Leave it as it is
|
||||
}
|
||||
}
|
||||
|
||||
rowSlice[i] = v
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -138,10 +138,10 @@ func Test_DisplayTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expected := `
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
`
|
||||
|
||||
// Test with non-pointer values.
|
||||
@@ -165,10 +165,10 @@ foo 10 [a b c] foo1 11 foo2 12 foo3
|
||||
t.Parallel()
|
||||
|
||||
expected := `
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
`
|
||||
|
||||
out, err := cliui.DisplayTable(in, "age", nil)
|
||||
@@ -235,12 +235,12 @@ Alice 25
|
||||
t.Run("WithSeparator", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
expected := `
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
|
||||
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
|
||||
`
|
||||
|
||||
var inlineIn []any
|
||||
|
||||
+5
-5
@@ -18,11 +18,10 @@ import (
|
||||
|
||||
func (r *RootCmd) organizations() *serpent.Command {
|
||||
cmd := &serpent.Command{
|
||||
Annotations: workspaceCommand,
|
||||
Use: "organizations [subcommand]",
|
||||
Short: "Organization related commands",
|
||||
Aliases: []string{"organization", "org", "orgs"},
|
||||
Hidden: true, // Hidden until these commands are complete.
|
||||
Use: "organizations [subcommand]",
|
||||
Short: "Organization related commands",
|
||||
Aliases: []string{"organization", "org", "orgs"},
|
||||
Hidden: true, // Hidden until these commands are complete.
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
return inv.Command.HelpHandler(inv)
|
||||
},
|
||||
@@ -31,6 +30,7 @@ func (r *RootCmd) organizations() *serpent.Command {
|
||||
r.switchOrganization(),
|
||||
r.createOrganization(),
|
||||
r.organizationRoles(),
|
||||
r.organizationMembers(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (r *RootCmd) organizationMembers() *serpent.Command {
|
||||
formatter := cliui.NewOutputFormatter(
|
||||
cliui.TableFormat([]codersdk.OrganizationMemberWithName{}, []string{"username", "organization_roles"}),
|
||||
cliui.JSONFormat(),
|
||||
)
|
||||
|
||||
client := new(codersdk.Client)
|
||||
cmd := &serpent.Command{
|
||||
Use: "members",
|
||||
Short: "List all organization members",
|
||||
Aliases: []string{"member"},
|
||||
Middleware: serpent.Chain(
|
||||
serpent.RequireNArgs(0),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
organization, err := CurrentOrganization(r, inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := client.OrganizationMembers(ctx, organization.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch members: %w", err)
|
||||
}
|
||||
|
||||
out, err := formatter.Format(inv.Context(), res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(inv.Stdout, out)
|
||||
return err
|
||||
},
|
||||
}
|
||||
formatter.AttachOptions(&cmd.Options)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestListOrganizationMembers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ownerClient := coderdtest.New(t, &coderdtest.Options{})
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "organization", "members", "-c", "user_id,username,roles")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
inv.Stdout = buf
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, buf.String(), user.Username)
|
||||
require.Contains(t, buf.String(), owner.UserID.String())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user