From d2998c6b7b9fe9f3d7922108dab536dbf08d6fc5 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 26 Feb 2024 10:03:49 -0600 Subject: [PATCH] feat: implement organization context in the cli (#12259) * feat: implement organization context in the cli `coder org show current` --- cli/config/file.go | 8 ++ cli/create.go | 2 +- cli/organization.go | 131 ++++++++++++++++++++++++ cli/organization_test.go | 45 ++++++++ cli/root.go | 41 +++++++- cli/templatecreate.go | 2 +- cli/templatecreate_test.go | 14 +-- cli/templatedelete.go | 2 +- cli/templateedit.go | 2 +- cli/templatelist.go | 2 +- cli/templatepull.go | 2 +- cli/templatepush.go | 2 +- cli/templateversionarchive.go | 4 +- cli/templateversions.go | 2 +- cli/usercreate.go | 2 +- coderd/httpmw/organizationparam.go | 27 ++++- coderd/httpmw/organizationparam_test.go | 18 +++- coderd/organizations_test.go | 6 +- codersdk/organizations.go | 20 ++-- codersdk/users.go | 2 +- enterprise/cli/groupcreate.go | 2 +- enterprise/cli/groupdelete.go | 2 +- enterprise/cli/groupedit.go | 2 +- enterprise/cli/grouplist.go | 2 +- 24 files changed, 290 insertions(+), 52 deletions(-) create mode 100644 cli/organization.go create mode 100644 cli/organization_test.go diff --git a/cli/config/file.go b/cli/config/file.go index 908af1aac8..48ca471217 100644 --- a/cli/config/file.go +++ b/cli/config/file.go @@ -70,6 +70,14 @@ func (r Root) PostgresPort() File { // File provides convenience methods for interacting with *os.File. type File string +func (f File) Exists() bool { + if f == "" { + return false + } + _, err := os.Stat(string(f)) + return err == nil +} + // Delete deletes the file. func (f File) Delete() error { if f == "" { diff --git a/cli/create.go b/cli/create.go index 1a2492374a..a923dc0d9a 100644 --- a/cli/create.go +++ b/cli/create.go @@ -43,7 +43,7 @@ func (r *RootCmd) create() *clibase.Cmd { ), Middleware: clibase.Chain(r.InitClient(client)), Handler: func(inv *clibase.Invocation) error { - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/organization.go b/cli/organization.go new file mode 100644 index 0000000000..455c0e9988 --- /dev/null +++ b/cli/organization.go @@ -0,0 +1,131 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/coder/coder/v2/cli/clibase" + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" +) + +func (r *RootCmd) organizations() *clibase.Cmd { + cmd := &clibase.Cmd{ + Annotations: workspaceCommand, + Use: "organizations [subcommand]", + Short: "Organization related commands", + Aliases: []string{"organization", "org", "orgs"}, + Hidden: true, // Hidden until these commands are complete. + Handler: func(inv *clibase.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*clibase.Cmd{ + r.currentOrganization(), + }, + } + + cmd.Options = clibase.OptionSet{} + return cmd +} + +func (r *RootCmd) currentOrganization() *clibase.Cmd { + var ( + stringFormat func(orgs []codersdk.Organization) (string, error) + client = new(codersdk.Client) + formatter = cliui.NewOutputFormatter( + cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) { + typed, ok := data.([]codersdk.Organization) + if !ok { + // This should never happen + return "", fmt.Errorf("expected []Organization, got %T", data) + } + return stringFormat(typed) + }), + cliui.TableFormat([]codersdk.Organization{}, []string{"id", "name", "default"}), + cliui.JSONFormat(), + ) + onlyID = false + ) + cmd := &clibase.Cmd{ + Use: "show [current|me|uuid]", + Short: "Show the organization, if no argument is given, the organization currently in use will be shown.", + Middleware: clibase.Chain( + r.InitClient(client), + clibase.RequireRangeArgs(0, 1), + ), + Options: clibase.OptionSet{ + { + Name: "only-id", + Description: "Only print the organization ID.", + Required: false, + Flag: "only-id", + Value: clibase.BoolOf(&onlyID), + }, + }, + Handler: func(inv *clibase.Invocation) error { + orgArg := "current" + if len(inv.Args) >= 1 { + orgArg = inv.Args[0] + } + + var orgs []codersdk.Organization + var err error + switch strings.ToLower(orgArg) { + case "current": + stringFormat = func(orgs []codersdk.Organization) (string, error) { + if len(orgs) != 1 { + return "", fmt.Errorf("expected 1 organization, got %d", len(orgs)) + } + return fmt.Sprintf("Current CLI Organization: %s (%s)\n", orgs[0].Name, orgs[0].ID.String()), nil + } + org, err := CurrentOrganization(r, inv, client) + if err != nil { + return err + } + orgs = []codersdk.Organization{org} + case "me": + stringFormat = func(orgs []codersdk.Organization) (string, error) { + var str strings.Builder + _, _ = fmt.Fprint(&str, "Organizations you are a member of:\n") + for _, org := range orgs { + _, _ = fmt.Fprintf(&str, "\t%s (%s)\n", org.Name, org.ID.String()) + } + return str.String(), nil + } + orgs, err = client.OrganizationsByUser(inv.Context(), codersdk.Me) + if err != nil { + return err + } + default: + stringFormat = func(orgs []codersdk.Organization) (string, error) { + if len(orgs) != 1 { + return "", fmt.Errorf("expected 1 organization, got %d", len(orgs)) + } + return fmt.Sprintf("Organization: %s (%s)\n", orgs[0].Name, orgs[0].ID.String()), nil + } + // This works for a uuid or a name + org, err := client.OrganizationByName(inv.Context(), orgArg) + if err != nil { + return err + } + orgs = []codersdk.Organization{org} + } + + if onlyID { + for _, org := range orgs { + _, _ = fmt.Fprintf(inv.Stdout, "%s\n", org.ID) + } + } else { + out, err := formatter.Format(inv.Context(), orgs) + if err != nil { + return err + } + _, _ = fmt.Fprint(inv.Stdout, out) + } + return nil + }, + } + formatter.AttachOptions(&cmd.Options) + + return cmd +} diff --git a/cli/organization_test.go b/cli/organization_test.go new file mode 100644 index 0000000000..658498883e --- /dev/null +++ b/cli/organization_test.go @@ -0,0 +1,45 @@ +package cli_test + +import ( + "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/codersdk" + "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" +) + +func TestCurrentOrganization(t *testing.T) { + t.Parallel() + + t.Run("OnlyID", func(t *testing.T) { + t.Parallel() + ownerClient := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, ownerClient) + // Owner is required to make orgs + client, _ := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID, rbac.RoleOwner()) + + ctx := testutil.Context(t, testutil.WaitMedium) + orgs := []string{"foo", "bar"} + for _, orgName := range orgs { + _, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{ + Name: orgName, + }) + require.NoError(t, err) + } + + inv, root := clitest.New(t, "organizations", "show", "--only-id") + clitest.SetupConfig(t, client, root) + pty := ptytest.New(t).Attach(inv) + errC := make(chan error) + go func() { + errC <- inv.Run() + }() + require.NoError(t, <-errC) + pty.ExpectMatch(first.OrganizationID.String()) + }) +} diff --git a/cli/root.go b/cli/root.go index 2bf0109557..4d78575c75 100644 --- a/cli/root.go +++ b/cli/root.go @@ -94,6 +94,7 @@ func (r *RootCmd) Core() []*clibase.Cmd { r.tokens(), r.users(), r.version(defaultVersionInfo), + r.organizations(), // Workspace Commands r.autoupdate(), @@ -698,14 +699,44 @@ func (r *RootCmd) createAgentClient() (*agentsdk.Client, error) { } // CurrentOrganization returns the currently active organization for the authenticated user. -func CurrentOrganization(inv *clibase.Invocation, client *codersdk.Client) (codersdk.Organization, error) { +func CurrentOrganization(r *RootCmd, inv *clibase.Invocation, client *codersdk.Client) (codersdk.Organization, error) { + conf := r.createConfig() + selected := "" + if conf.Organization().Exists() { + org, err := conf.Organization().Read() + if err != nil { + return codersdk.Organization{}, fmt.Errorf("read selected organization from config file %q: %w", conf.Organization(), err) + } + selected = org + } + + // Verify the org exists and the user is a member orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me) if err != nil { - return codersdk.Organization{}, nil + return codersdk.Organization{}, err } - // For now, we won't use the config to set this. - // Eventually, we will support changing using "coder switch " - return orgs[0], nil + + // User manually selected an organization + if selected != "" { + index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { + return org.Name == selected || org.ID.String() == selected + }) + + if index < 0 { + return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization?", selected) + } + return orgs[index], nil + } + + // User did not select an organization, so use the default. + index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { + return org.IsDefault + }) + if index < 0 { + return codersdk.Organization{}, xerrors.Errorf("unable to determine current organization. Use 'coder organizations switch ' to select an organization to use") + } + + return orgs[index], nil } func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) { diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 5a03ff1267..4e379ef67a 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -69,7 +69,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { } } - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/templatecreate_test.go b/cli/templatecreate_test.go index 0eaf1344ea..9710a86a88 100644 --- a/cli/templatecreate_test.go +++ b/cli/templatecreate_test.go @@ -243,19 +243,7 @@ func TestTemplateCreate(t *testing.T) { assert.Error(t, err) }() - matches := []struct { - match string - write string - }{ - {match: "Upload", write: "yes"}, - } - for _, m := range matches { - pty.ExpectMatch(m.match) - if len(m.write) > 0 { - pty.WriteLine(m.write) - } - } - + pty.ExpectMatch("context canceled") <-ctx.Done() }) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index e15fe4bd48..ac6603815b 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -32,7 +32,7 @@ func (r *RootCmd) templateDelete() *clibase.Cmd { templates = []codersdk.Template{} ) - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/templateedit.go b/cli/templateedit.go index c7ac3b430b..b28375eabb 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -79,7 +79,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { } } - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } diff --git a/cli/templatelist.go b/cli/templatelist.go index 6e18f84625..d6fcbd77da 100644 --- a/cli/templatelist.go +++ b/cli/templatelist.go @@ -25,7 +25,7 @@ func (r *RootCmd) templateList() *clibase.Cmd { r.InitClient(client), ), Handler: func(inv *clibase.Invocation) error { - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/templatepull.go b/cli/templatepull.go index 2509cc9df4..36fd214561 100644 --- a/cli/templatepull.go +++ b/cli/templatepull.go @@ -44,7 +44,7 @@ func (r *RootCmd) templatePull() *clibase.Cmd { return xerrors.Errorf("either tar or zip can be selected") } - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } diff --git a/cli/templatepush.go b/cli/templatepush.go index e0e1e689c4..7e60a7bcb9 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -46,7 +46,7 @@ func (r *RootCmd) templatePush() *clibase.Cmd { Handler: func(inv *clibase.Invocation) error { uploadFlags.setWorkdir(workdir) - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/templateversionarchive.go b/cli/templateversionarchive.go index 63c9d8a3de..4223809029 100644 --- a/cli/templateversionarchive.go +++ b/cli/templateversionarchive.go @@ -47,7 +47,7 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *clibase.Cmd { versions []codersdk.TemplateVersion ) - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } @@ -121,7 +121,7 @@ func (r *RootCmd) archiveTemplateVersions() *clibase.Cmd { templates = []codersdk.Template{} ) - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/cli/templateversions.go b/cli/templateversions.go index a27d6a6d65..1fc0902a7d 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -93,7 +93,7 @@ func (r *RootCmd) templateVersionsList() *clibase.Cmd { }, }, Handler: func(inv *clibase.Invocation) error { - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } diff --git a/cli/usercreate.go b/cli/usercreate.go index 478cc98e16..8fe7027eac 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -31,7 +31,7 @@ func (r *RootCmd) userCreate() *clibase.Cmd { r.InitClient(client), ), Handler: func(inv *clibase.Invocation) error { - organization, err := CurrentOrganization(inv, client) + organization, err := CurrentOrganization(r, inv, client) if err != nil { return err } diff --git a/coderd/httpmw/organizationparam.go b/coderd/httpmw/organizationparam.go index 0637fba3dc..c219751e2b 100644 --- a/coderd/httpmw/organizationparam.go +++ b/coderd/httpmw/organizationparam.go @@ -2,8 +2,12 @@ package httpmw import ( "context" + "fmt" "net/http" + "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/httpapi" @@ -40,19 +44,34 @@ func ExtractOrganizationParam(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) { ctx := r.Context() - orgID, ok := ParseUUIDParam(rw, r, "organization") - if !ok { + arg := chi.URLParam(r, "organization") + if arg == "" { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "\"organization\" must be provided.", + }) return } - organization, err := db.GetOrganizationByID(ctx, orgID) + var organization database.Organization + var err error + // Try by name or uuid. + id, err := uuid.Parse(arg) + if err == nil { + organization, err = db.GetOrganizationByID(ctx, id) + } else { + organization, err = db.GetOrganizationByName(ctx, arg) + } if httpapi.Is404Error(err) { httpapi.ResourceNotFound(rw) + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ + Message: fmt.Sprintf("Organization %q not found.", arg), + Detail: "Provide either the organization id or name.", + }) return } if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching organization.", + Message: fmt.Sprintf("Internal error fetching organization %q.", arg), Detail: err.Error(), }) return diff --git a/coderd/httpmw/organizationparam_test.go b/coderd/httpmw/organizationparam_test.go index d9cf0c7913..e5415d1348 100644 --- a/coderd/httpmw/organizationparam_test.go +++ b/coderd/httpmw/organizationparam_test.go @@ -103,7 +103,7 @@ func TestOrganizationParam(t *testing.T) { rtr.ServeHTTP(rw, r) res := rw.Result() defer res.Body.Close() - require.Equal(t, http.StatusBadRequest, res.StatusCode) + require.Equal(t, http.StatusNotFound, res.StatusCode) }) t.Run("NotInOrganization", func(t *testing.T) { @@ -160,8 +160,6 @@ func TestOrganizationParam(t *testing.T) { }) require.NoError(t, err) - chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String()) - chi.RouteContext(r.Context()).URLParams.Add("user", user.ID.String()) rtr.Use( httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{ DB: db, @@ -194,9 +192,21 @@ func TestOrganizationParam(t *testing.T) { assert.NotEmpty(t, orgMem.OrganizationMember.UserID) assert.NotEmpty(t, orgMem.OrganizationMember.Roles) }) + + // Try by ID + chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String()) + chi.RouteContext(r.Context()).URLParams.Add("user", user.ID.String()) rtr.ServeHTTP(rw, r) res := rw.Result() defer res.Body.Close() - require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, http.StatusOK, res.StatusCode, "by id") + + // Try by name + chi.RouteContext(r.Context()).URLParams.Add("organization", organization.Name) + chi.RouteContext(r.Context()).URLParams.Add("user", user.ID.String()) + rtr.ServeHTTP(rw, r) + res = rw.Result() + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode, "by name") }) } diff --git a/coderd/organizations_test.go b/coderd/organizations_test.go index b146925667..e176c7a6d8 100644 --- a/coderd/organizations_test.go +++ b/coderd/organizations_test.go @@ -66,7 +66,7 @@ func TestOrganizationByUserAndName(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - _, err := client.OrganizationByName(ctx, codersdk.Me, "nothing") + _, err := client.OrganizationByUserAndName(ctx, codersdk.Me, "nothing") var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -85,7 +85,7 @@ func TestOrganizationByUserAndName(t *testing.T) { Name: "another", }) require.NoError(t, err) - _, err = other.OrganizationByName(ctx, codersdk.Me, org.Name) + _, err = other.OrganizationByUserAndName(ctx, codersdk.Me, org.Name) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -101,7 +101,7 @@ func TestOrganizationByUserAndName(t *testing.T) { org, err := client.Organization(ctx, user.OrganizationID) require.NoError(t, err) - _, err = client.OrganizationByName(ctx, codersdk.Me, org.Name) + _, err = client.OrganizationByUserAndName(ctx, codersdk.Me, org.Name) require.NoError(t, err) }) } diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 8bf1ecc968..55e2a6b1ab 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -26,11 +26,11 @@ const ( // Organization is the JSON representation of a Coder organization. type Organization struct { - ID uuid.UUID `json:"id" validate:"required" format:"uuid"` - Name string `json:"name" validate:"required"` - CreatedAt time.Time `json:"created_at" validate:"required" format:"date-time"` - UpdatedAt time.Time `json:"updated_at" validate:"required" format:"date-time"` - IsDefault bool `json:"is_default" validate:"required"` + ID uuid.UUID `table:"id" json:"id" validate:"required" format:"uuid"` + Name string `table:"name,default_sort" json:"name" validate:"required"` + CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"` + UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"` + IsDefault bool `table:"default" json:"is_default" validate:"required"` } type OrganizationMember struct { @@ -153,8 +153,8 @@ type CreateWorkspaceRequest struct { AutomaticUpdates AutomaticUpdates `json:"automatic_updates,omitempty"` } -func (c *Client) Organization(ctx context.Context, id uuid.UUID) (Organization, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s", id.String()), nil) +func (c *Client) OrganizationByName(ctx context.Context, name string) (Organization, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s", name), nil) if err != nil { return Organization{}, xerrors.Errorf("execute request: %w", err) } @@ -168,6 +168,12 @@ func (c *Client) Organization(ctx context.Context, id uuid.UUID) (Organization, return organization, json.NewDecoder(res.Body).Decode(&organization) } +func (c *Client) Organization(ctx context.Context, id uuid.UUID) (Organization, error) { + // OrganizationByName uses the exact same endpoint. It accepts a name or uuid. + // We just provide this function for type safety. + return c.OrganizationByName(ctx, id.String()) +} + // ProvisionerDaemons returns provisioner daemons available. func (c *Client) ProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) { res, err := c.Request(ctx, http.MethodGet, diff --git a/codersdk/users.go b/codersdk/users.go index b42eee38af..1d50df1472 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -573,7 +573,7 @@ func (c *Client) OrganizationsByUser(ctx context.Context, user string) ([]Organi return orgs, json.NewDecoder(res.Body).Decode(&orgs) } -func (c *Client) OrganizationByName(ctx context.Context, user string, name string) (Organization, error) { +func (c *Client) OrganizationByUserAndName(ctx context.Context, user string, name string) (Organization, error) { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", user, name), nil) if err != nil { return Organization{}, err diff --git a/enterprise/cli/groupcreate.go b/enterprise/cli/groupcreate.go index e5f7bbd8a3..8a83b1551c 100644 --- a/enterprise/cli/groupcreate.go +++ b/enterprise/cli/groupcreate.go @@ -29,7 +29,7 @@ func (r *RootCmd) groupCreate() *clibase.Cmd { Handler: func(inv *clibase.Invocation) error { ctx := inv.Context() - org, err := agpl.CurrentOrganization(inv, client) + org, err := agpl.CurrentOrganization(&r.RootCmd, inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) } diff --git a/enterprise/cli/groupdelete.go b/enterprise/cli/groupdelete.go index e7ca01ba36..4fab590178 100644 --- a/enterprise/cli/groupdelete.go +++ b/enterprise/cli/groupdelete.go @@ -27,7 +27,7 @@ func (r *RootCmd) groupDelete() *clibase.Cmd { groupName = inv.Args[0] ) - org, err := agpl.CurrentOrganization(inv, client) + org, err := agpl.CurrentOrganization(&r.RootCmd, inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) } diff --git a/enterprise/cli/groupedit.go b/enterprise/cli/groupedit.go index 8811378bc0..b6c730d395 100644 --- a/enterprise/cli/groupedit.go +++ b/enterprise/cli/groupedit.go @@ -37,7 +37,7 @@ func (r *RootCmd) groupEdit() *clibase.Cmd { groupName = inv.Args[0] ) - org, err := agpl.CurrentOrganization(inv, client) + org, err := agpl.CurrentOrganization(&r.RootCmd, inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) } diff --git a/enterprise/cli/grouplist.go b/enterprise/cli/grouplist.go index 78bcb28ca1..92a45988ba 100644 --- a/enterprise/cli/grouplist.go +++ b/enterprise/cli/grouplist.go @@ -30,7 +30,7 @@ func (r *RootCmd) groupList() *clibase.Cmd { Handler: func(inv *clibase.Invocation) error { ctx := inv.Context() - org, err := agpl.CurrentOrganization(inv, client) + org, err := agpl.CurrentOrganization(&r.RootCmd, inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) }