Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func create() *cobra.Command {
return err
}

organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func login() *cobra.Command {
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
}
if !hasInitialUser {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"Your Coder deployment hasn't been set up!\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Caret+"Your Coder deployment hasn't been set up!\n")

if username == "" {
if !isTTY(cmd) {
Expand Down Expand Up @@ -244,7 +244,7 @@ func login() *cobra.Command {
return xerrors.Errorf("write server url: %w", err)
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"Welcome to Coder, %s! You're authenticated.\n", cliui.Styles.Keyword.Render(resp.Username))
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Caret+"Welcome to Coder, %s! You're authenticated.\n", cliui.Styles.Keyword.Render(resp.Username))
return nil
},
}
Expand Down
2 changes: 1 addition & 1 deletion cli/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func logout() *cobra.Command {
errorString := strings.TrimRight(errorStringBuilder.String(), "\n")
return xerrors.New("Failed to log out.\n" + errorString)
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"You are no longer logged in. You can log in using 'coder login <url>'.\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Caret+"You are no longer logged in. You can log in using 'coder login <url>'.\n")
return nil
},
}
Expand Down
2 changes: 1 addition & 1 deletion cli/parameterslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func parameterList() *cobra.Command {
return err
}

organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return xerrors.Errorf("get current organization: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

var (
caret = cliui.Styles.Prompt.String()
Caret = cliui.Styles.Prompt.String()

// Applied as annotations to workspace commands
// so they display in a separated "help" section.
Expand Down Expand Up @@ -352,8 +352,8 @@ func createAgentClient(cmd *cobra.Command) (*codersdk.Client, error) {
return client, nil
}

// currentOrganization returns the currently active organization for the authenticated user.
func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk.Organization, error) {
// CurrentOrganization returns the currently active organization for the authenticated user.
func CurrentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk.Organization, error) {
orgs, err := client.OrganizationsByUser(cmd.Context(), codersdk.Me)
if err != nil {
return codersdk.Organization{}, nil
Expand Down
2 changes: 1 addition & 1 deletion cli/templatecreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func templateCreate() *cobra.Command {
return err
}

organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/templatedelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func templateDelete() *cobra.Command {
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/templateedit.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func templateEdit() *cobra.Command {
if err != nil {
return xerrors.Errorf("create client: %w", err)
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return xerrors.Errorf("get current organization: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions cli/templatelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func templateList() *cobra.Command {
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand All @@ -30,7 +30,7 @@ func templateList() *cobra.Command {
}

if len(templates) == 0 {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s No templates found in %s! Create one:\n\n", caret, color.HiWhiteString(organization.Name))
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s No templates found in %s! Create one:\n\n", Caret, color.HiWhiteString(organization.Name))
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), color.HiMagentaString(" $ coder templates create <directory>\n"))
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cli/templatepull.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func templatePull() *cobra.Command {
}

// TODO(JonA): Do we need to add a flag for organization?
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return xerrors.Errorf("current organization: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/templatepush.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func templatePush() *cobra.Command {
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/templateversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func templateVersionsList() *cobra.Command {
if err != nil {
return xerrors.Errorf("create client: %w", err)
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return xerrors.Errorf("get current organization: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/usercreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func userCreate() *cobra.Command {
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
organization, err := CurrentOrganization(cmd, client)
if err != nil {
return err
}
Expand Down
37 changes: 37 additions & 0 deletions coderd/httpmw/groupparam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"

"github.com/go-chi/chi/v5"
"golang.org/x/xerrors"

"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
Expand All @@ -24,6 +25,42 @@ func GroupParam(r *http.Request) database.Group {
return group
}

func ExtractGroupByNameParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
org = OrganizationParam(r)
)

name := chi.URLParam(r, "groupName")
if name == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Missing group name in URL",
})
return
}

group, err := db.GetGroupByOrgAndName(ctx, database.GetGroupByOrgAndNameParams{
OrganizationID: org.ID,
Name: name,
})
if xerrors.Is(err, sql.ErrNoRows) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

ctx = context.WithValue(ctx, groupParamContextKey{}, group)
chi.RouteContext(ctx).URLParams.Add("organization", group.OrganizationID.String())
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

// ExtraGroupParam grabs a group from the "group" URL parameter.
func ExtractGroupParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
Expand Down
17 changes: 17 additions & 0 deletions codersdk/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ func (c *Client) GroupsByOrganization(ctx context.Context, orgID uuid.UUID) ([]G
return groups, json.NewDecoder(res.Body).Decode(&groups)
}

func (c *Client) GroupByOrgAndName(ctx context.Context, orgID uuid.UUID, name string) (Group, error) {
res, err := c.Request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/organizations/%s/groups/%s", orgID.String(), name),
nil,
)
if err != nil {
return Group{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return Group{}, readBodyAsError(res)
}
var resp Group
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

func (c *Client) Group(ctx context.Context, group uuid.UUID) (Group, error) {
res, err := c.Request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/groups/%s", group.String()),
Expand Down
54 changes: 54 additions & 0 deletions enterprise/cli/groupcreate.go
Original file line number Diff line number Diff line change
@@ -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
}
48 changes: 48 additions & 0 deletions enterprise/cli/groupcreate_test.go
Original file line number Diff line number Diff line change
@@ -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)))
})
}
50 changes: 50 additions & 0 deletions enterprise/cli/groupdelete.go
Original file line number Diff line number Diff line change
@@ -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("patch group: %w", err)
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Successfully deleted group %s!\n", cliui.Styles.Keyword.Render(group.Name))
return nil
},
}

return cmd
}
Loading