mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add groups support to the CLI (#4755)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agpl "github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/cliflag"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func groupCreate() *cobra.Command {
|
||||
var (
|
||||
avatarURL string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <name>",
|
||||
Short: "Create a user group",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
ctx = cmd.Context()
|
||||
)
|
||||
|
||||
client, err := agpl.CreateClient(cmd)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
|
||||
org, err := agpl.CurrentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("current organization: %w", err)
|
||||
}
|
||||
|
||||
group, err := client.CreateGroup(ctx, org.ID, codersdk.CreateGroupRequest{
|
||||
Name: args[0],
|
||||
AvatarURL: avatarURL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create group: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Successfully created group %s!\n", cliui.Styles.Keyword.Render(group.Name))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cliflag.StringVarP(cmd.Flags(), &avatarURL, "avatar-url", "u", "CODER_AVATAR_URL", "", "set an avatar for a group")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/enterprise/cli"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
)
|
||||
|
||||
func TestCreateGroup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
var (
|
||||
groupName = "test"
|
||||
avatarURL = "https://example.com"
|
||||
)
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups",
|
||||
"create", groupName,
|
||||
"--avatar-url", avatarURL,
|
||||
)
|
||||
|
||||
pty := ptytest.New(t)
|
||||
cmd.SetOut(pty.Output())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
pty.ExpectMatch(fmt.Sprintf("Successfully created group %s!", cliui.Styles.Keyword.Render(groupName)))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agpl "github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
)
|
||||
|
||||
func groupDelete() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <name>",
|
||||
Short: "Delete a user group",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
ctx = cmd.Context()
|
||||
groupName = args[0]
|
||||
)
|
||||
|
||||
client, err := agpl.CreateClient(cmd)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
|
||||
org, err := agpl.CurrentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("current organization: %w", err)
|
||||
}
|
||||
|
||||
group, err := client.GroupByOrgAndName(ctx, org.ID, groupName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("group by org and name: %w", err)
|
||||
}
|
||||
|
||||
err = client.DeleteGroup(ctx, group.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("delete group: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Successfully deleted group %s!\n", cliui.Styles.Keyword.Render(group.Name))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/cli"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestGroupDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
ctx, _ := testutil.Context(t)
|
||||
group, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "alpha",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(),
|
||||
"groups", "delete", group.Name,
|
||||
)
|
||||
|
||||
pty := ptytest.New(t)
|
||||
|
||||
cmd.SetOut(pty.Output())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err = cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
pty.ExpectMatch(fmt.Sprintf("Successfully deleted group %s", cliui.Styles.Keyword.Render(group.Name)))
|
||||
})
|
||||
|
||||
t.Run("NoArg", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(),
|
||||
"groups", "delete")
|
||||
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/mail"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agpl "github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/cliflag"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func groupEdit() *cobra.Command {
|
||||
var (
|
||||
avatarURL string
|
||||
name string
|
||||
addUsers []string
|
||||
rmUsers []string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit <name>",
|
||||
Short: "Edit a user group",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
ctx = cmd.Context()
|
||||
groupName = args[0]
|
||||
)
|
||||
|
||||
client, err := agpl.CreateClient(cmd)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
|
||||
org, err := agpl.CurrentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("current organization: %w", err)
|
||||
}
|
||||
|
||||
group, err := client.GroupByOrgAndName(ctx, org.ID, groupName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("group by org and name: %w", err)
|
||||
}
|
||||
|
||||
req := codersdk.PatchGroupRequest{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if avatarURL != "" {
|
||||
req.AvatarURL = &avatarURL
|
||||
}
|
||||
|
||||
users, err := client.Users(ctx, codersdk.UsersRequest{})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get users: %w", err)
|
||||
}
|
||||
|
||||
req.AddUsers, err = convertToUserIDs(addUsers, users)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse add-users: %w", err)
|
||||
}
|
||||
|
||||
req.RemoveUsers, err = convertToUserIDs(rmUsers, users)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse rm-users: %w", err)
|
||||
}
|
||||
|
||||
group, err = client.PatchGroup(ctx, group.ID, req)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("patch group: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Successfully patched group %s!\n", cliui.Styles.Keyword.Render(group.Name))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cliflag.StringVarP(cmd.Flags(), &name, "name", "n", "", "", "Update the group name")
|
||||
cliflag.StringVarP(cmd.Flags(), &avatarURL, "avatar-url", "u", "", "", "Update the group avatar")
|
||||
cliflag.StringArrayVarP(cmd.Flags(), &addUsers, "add-users", "a", "", nil, "Add users to the group. Accepts emails or IDs.")
|
||||
cliflag.StringArrayVarP(cmd.Flags(), &rmUsers, "rm-users", "r", "", nil, "Remove users to the group. Accepts emails or IDs.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// convertToUserIDs accepts a list of users in the form of IDs or email addresses
|
||||
// and translates any emails to the matching user ID.
|
||||
func convertToUserIDs(userList []string, users []codersdk.User) ([]string, error) {
|
||||
converted := make([]string, 0, len(userList))
|
||||
|
||||
for _, user := range userList {
|
||||
if _, err := uuid.Parse(user); err == nil {
|
||||
converted = append(converted, user)
|
||||
continue
|
||||
}
|
||||
if _, err := mail.ParseAddress(user); err == nil {
|
||||
for _, u := range users {
|
||||
if u.Email == user {
|
||||
converted = append(converted, u.ID.String())
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, xerrors.Errorf("%q must be a valid UUID or email address", user)
|
||||
}
|
||||
|
||||
return converted, nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/cli"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestGroupEdit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
ctx, _ := testutil.Context(t)
|
||||
_, user1 := coderdtest.CreateAnotherUserWithUser(t, client, admin.OrganizationID)
|
||||
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, admin.OrganizationID)
|
||||
_, user3 := coderdtest.CreateAnotherUserWithUser(t, client, admin.OrganizationID)
|
||||
|
||||
group, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "alpha",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// We use the sdk here as opposed to the CLI since adding this user
|
||||
// is considered setup. They will be removed in the proper CLI test.
|
||||
group, err = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
|
||||
AddUsers: []string{user3.ID.String()},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
expectedName = "beta"
|
||||
)
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(),
|
||||
"groups", "edit", group.Name,
|
||||
"--name", expectedName,
|
||||
"--avatar-url", "https://example.com",
|
||||
"-a", user1.ID.String(),
|
||||
"-a", user2.Email,
|
||||
"-r", user3.ID.String(),
|
||||
)
|
||||
|
||||
pty := ptytest.New(t)
|
||||
|
||||
cmd.SetOut(pty.Output())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err = cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
pty.ExpectMatch(fmt.Sprintf("Successfully patched group %s", cliui.Styles.Keyword.Render(expectedName)))
|
||||
})
|
||||
|
||||
t.Run("InvalidUserInput", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
ctx, _ := testutil.Context(t)
|
||||
|
||||
group, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "alpha",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(),
|
||||
"groups", "edit", group.Name,
|
||||
"-a", "foo",
|
||||
)
|
||||
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err = cmd.Execute()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "must be a valid UUID or email address")
|
||||
})
|
||||
|
||||
t.Run("NoArg", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups", "edit")
|
||||
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := cmd.Execute()
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agpl "github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func groupList() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List user groups",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
ctx = cmd.Context()
|
||||
)
|
||||
|
||||
client, err := agpl.CreateClient(cmd)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
|
||||
org, err := agpl.CurrentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("current organization: %w", err)
|
||||
}
|
||||
|
||||
groups, err := client.GroupsByOrganization(ctx, org.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get groups: %w", err)
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s No groups found in %s! Create one:\n\n", agpl.Caret, color.HiWhiteString(org.Name))
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), color.HiMagentaString(" $ coder groups create <name>\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := displayGroups(groups...)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("display groups: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
type groupTableRow struct {
|
||||
Name string `table:"name"`
|
||||
OrganizationID uuid.UUID `table:"organization_id"`
|
||||
Members []string `table:"members"`
|
||||
AvatarURL string `table:"avatar_url"`
|
||||
}
|
||||
|
||||
func displayGroups(groups ...codersdk.Group) (string, error) {
|
||||
rows := make([]groupTableRow, 0, len(groups))
|
||||
for _, group := range groups {
|
||||
members := make([]string, 0, len(group.Members))
|
||||
for _, member := range group.Members {
|
||||
members = append(members, member.Email)
|
||||
}
|
||||
rows = append(rows, groupTableRow{
|
||||
Name: group.Name,
|
||||
OrganizationID: group.OrganizationID,
|
||||
AvatarURL: group.AvatarURL,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
return cliui.DisplayTable(rows, "name", nil)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/cli"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestGroupList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
ctx, _ := testutil.Context(t)
|
||||
_, user1 := coderdtest.CreateAnotherUserWithUser(t, client, admin.OrganizationID)
|
||||
_, user2 := coderdtest.CreateAnotherUserWithUser(t, client, admin.OrganizationID)
|
||||
|
||||
// We intentionally create the first group as beta so that we
|
||||
// can assert that things are being sorted by name intentionally
|
||||
// and not by chance (or some other parameter like created_at).
|
||||
group1, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "beta",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
group2, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{
|
||||
Name: "alpha",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.PatchGroup(ctx, group1.ID, codersdk.PatchGroupRequest{
|
||||
AddUsers: []string{user1.ID.String()},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.PatchGroup(ctx, group2.ID, codersdk.PatchGroupRequest{
|
||||
AddUsers: []string{user2.ID.String()},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups", "list")
|
||||
|
||||
pty := ptytest.New(t)
|
||||
|
||||
cmd.SetOut(pty.Output())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err = cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
matches := []string{"NAME", "ORGANIZATION ID", "MEMBERS", " AVATAR URL",
|
||||
group2.Name, group2.OrganizationID.String(), user2.Email, group2.AvatarURL,
|
||||
group1.Name, group1.OrganizationID.String(), user1.Email, group1.AvatarURL,
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
pty.ExpectMatch(match)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoGroups", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups", "list")
|
||||
|
||||
pty := ptytest.New(t)
|
||||
|
||||
cmd.SetErr(pty.Output())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := cmd.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
pty.ExpectMatch("No groups found")
|
||||
pty.ExpectMatch("coder groups create <name>")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func groups() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "groups",
|
||||
Short: "Manage groups",
|
||||
Aliases: []string{"group"},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
groupCreate(),
|
||||
groupList(),
|
||||
groupEdit(),
|
||||
groupDelete(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -11,6 +11,7 @@ func enterpriseOnly() []*cobra.Command {
|
||||
server(),
|
||||
features(),
|
||||
licenses(),
|
||||
groups(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user