diff --git a/cli/create.go b/cli/create.go index 46d67c22663d2..df60fdd72d345 100644 --- a/cli/create.go +++ b/cli/create.go @@ -29,6 +29,7 @@ func (r *RootCmd) create() *serpent.Command { parameterFlags workspaceParameterFlags autoUpdates string copyParametersFrom string + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) cmd := &serpent.Command{ @@ -43,7 +44,7 @@ func (r *RootCmd) create() *serpent.Command { ), Middleware: serpent.Chain(r.InitClient(client)), Handler: func(inv *serpent.Invocation) error { - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -269,6 +270,7 @@ func (r *RootCmd) create() *serpent.Command { ) cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...) cmd.Options = append(cmd.Options, parameterFlags.cliParameterDefaults()...) + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/login.go b/cli/login.go index 7dde98b118c5d..834ba73ce38a0 100644 --- a/cli/login.go +++ b/cli/login.go @@ -358,13 +358,6 @@ func (r *RootCmd) login() *serpent.Command { return xerrors.Errorf("write server url: %w", err) } - // If the current organization cannot be fetched, then reset the organization context. - // Otherwise, organization cli commands will fail. - _, err = CurrentOrganization(r, inv, client) - if err != nil { - _ = config.Organization().Delete() - } - _, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n", pretty.Sprint(cliui.DefaultStyles.Keyword, resp.Username)) return nil }, diff --git a/cli/login_test.go b/cli/login_test.go index b2f93ad5e6813..0428c332d02b0 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -5,11 +5,9 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "runtime" "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -424,29 +422,6 @@ func TestLogin(t *testing.T) { require.NotEqual(t, client.SessionToken(), sessionFile) }) - // Login should reset the configured organization if the user is not a member - t.Run("ResetOrganization", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, nil) - coderdtest.CreateFirstUser(t, client) - root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken()) - - notRealOrg := uuid.NewString() - err := cfg.Organization().Write(notRealOrg) - require.NoError(t, err, "write bad org to config") - - err = root.Run() - require.NoError(t, err) - sessionFile, err := cfg.Session().Read() - require.NoError(t, err) - require.NotEqual(t, client.SessionToken(), sessionFile) - - // Organization config should be deleted since the org does not exist - selected, err := cfg.Organization().Read() - require.ErrorIs(t, err, os.ErrNotExist) - require.NotEqual(t, selected, notRealOrg) - }) - t.Run("KeepOrganizationContext", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) diff --git a/cli/organization.go b/cli/organization.go index 44f9c3308139e..42648a564168a 100644 --- a/cli/organization.go +++ b/cli/organization.go @@ -1,22 +1,19 @@ package cli import ( - "errors" "fmt" - "os" - "slices" "strings" "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/cli/config" "github.com/coder/coder/v2/codersdk" - "github.com/coder/pretty" "github.com/coder/serpent" ) func (r *RootCmd) organizations() *serpent.Command { + orgContext := NewOrganizationContext() + cmd := &serpent.Command{ Use: "organizations [subcommand]", Short: "Organization related commands", @@ -26,188 +23,18 @@ func (r *RootCmd) organizations() *serpent.Command { return inv.Command.HelpHandler(inv) }, Children: []*serpent.Command{ - r.currentOrganization(), - r.switchOrganization(), + r.showOrganization(orgContext), r.createOrganization(), - r.organizationMembers(), - r.organizationRoles(), + r.organizationMembers(orgContext), + r.organizationRoles(orgContext), }, } - cmd.Options = serpent.OptionSet{} + orgContext.AttachOptions(cmd) return cmd } -func (r *RootCmd) switchOrganization() *serpent.Command { - client := new(codersdk.Client) - - cmd := &serpent.Command{ - Use: "set ", - Short: "set the organization used by the CLI. Pass an empty string to reset to the default organization.", - Long: "set the organization used by the CLI. Pass an empty string to reset to the default organization.\n" + FormatExamples( - Example{ - Description: "Remove the current organization and defer to the default.", - Command: "coder organizations set ''", - }, - Example{ - Description: "Switch to a custom organization.", - Command: "coder organizations set my-org", - }, - ), - Middleware: serpent.Chain( - r.InitClient(client), - serpent.RequireRangeArgs(0, 1), - ), - Options: serpent.OptionSet{}, - Handler: func(inv *serpent.Invocation) error { - conf := r.createConfig() - orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me) - if err != nil { - return xerrors.Errorf("failed to get organizations: %w", err) - } - // Keep the list of orgs sorted - slices.SortFunc(orgs, func(a, b codersdk.Organization) int { - return strings.Compare(a.Name, b.Name) - }) - - var switchToOrg string - if len(inv.Args) == 0 { - // Pull switchToOrg from a prompt selector, rather than command line - // args. - switchToOrg, err = promptUserSelectOrg(inv, conf, orgs) - if err != nil { - return err - } - } else { - switchToOrg = inv.Args[0] - } - - // If the user passes an empty string, we want to remove the organization - // from the config file. This will defer to default behavior. - if switchToOrg == "" { - err := conf.Organization().Delete() - if err != nil && !errors.Is(err, os.ErrNotExist) { - return xerrors.Errorf("failed to unset organization: %w", err) - } - _, _ = fmt.Fprintf(inv.Stdout, "Organization unset\n") - } else { - // Find the selected org in our list. - index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { - return org.Name == switchToOrg || org.ID.String() == switchToOrg - }) - if index < 0 { - // Using this error for better error message formatting - err := &codersdk.Error{ - Response: codersdk.Response{ - Message: fmt.Sprintf("Organization %q not found. Is the name correct, and are you a member of it?", switchToOrg), - Detail: "Ensure the organization argument is correct and you are a member of it.", - }, - Helper: fmt.Sprintf("Valid organizations you can switch to: %s", strings.Join(orgNames(orgs), ", ")), - } - return err - } - - // Always write the uuid to the config file. Names can change. - err := conf.Organization().Write(orgs[index].ID.String()) - if err != nil { - return xerrors.Errorf("failed to write organization to config file: %w", err) - } - } - - // Verify it worked. - current, err := CurrentOrganization(r, inv, client) - if err != nil { - // An SDK error could be a permission error. So offer the advice to unset the org - // and reset the context. - var sdkError *codersdk.Error - if errors.As(err, &sdkError) { - if sdkError.Helper == "" && sdkError.StatusCode() != 500 { - sdkError.Helper = `If this error persists, try unsetting your org with 'coder organizations set ""'` - } - return sdkError - } - return xerrors.Errorf("failed to get current organization: %w", err) - } - - _, _ = fmt.Fprintf(inv.Stdout, "Current organization context set to %s (%s)\n", current.Name, current.ID.String()) - return nil - }, - } - - return cmd -} - -// promptUserSelectOrg will prompt the user to select an organization from a list -// of their organizations. -func promptUserSelectOrg(inv *serpent.Invocation, conf config.Root, orgs []codersdk.Organization) (string, error) { - // Default choice - var defaultOrg string - // Comes from config file - if conf.Organization().Exists() { - defaultOrg, _ = conf.Organization().Read() - } - - // No config? Comes from default org in the list - if defaultOrg == "" { - defIndex := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { - return org.IsDefault - }) - if defIndex >= 0 { - defaultOrg = orgs[defIndex].Name - } - } - - // Defer to first org - if defaultOrg == "" && len(orgs) > 0 { - defaultOrg = orgs[0].Name - } - - // Ensure the `defaultOrg` value is an org name, not a uuid. - // If it is a uuid, change it to the org name. - index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { - return org.ID.String() == defaultOrg || org.Name == defaultOrg - }) - if index >= 0 { - defaultOrg = orgs[index].Name - } - - // deselectOption is the option to delete the organization config file and defer - // to default behavior. - const deselectOption = "[Default]" - if defaultOrg == "" { - defaultOrg = deselectOption - } - - // Pull value from a prompt - _, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select an organization below to set the current CLI context to:")) - value, err := cliui.Select(inv, cliui.SelectOptions{ - Options: append([]string{deselectOption}, orgNames(orgs)...), - Default: defaultOrg, - Size: 10, - HideSearch: false, - }) - if err != nil { - return "", err - } - // Deselect is an alias for "" - if value == deselectOption { - value = "" - } - - return value, nil -} - -// orgNames is a helper function to turn a list of organizations into a list of -// their names as strings. -func orgNames(orgs []codersdk.Organization) []string { - names := make([]string, 0, len(orgs)) - for _, org := range orgs { - names = append(names, org.Name) - } - return names -} - -func (r *RootCmd) currentOrganization() *serpent.Command { +func (r *RootCmd) showOrganization(orgContext *OrganizationContext) *serpent.Command { var ( stringFormat func(orgs []codersdk.Organization) (string, error) client = new(codersdk.Client) @@ -226,8 +53,29 @@ func (r *RootCmd) currentOrganization() *serpent.Command { onlyID = false ) cmd := &serpent.Command{ - Use: "show [current|me|uuid]", - Short: "Show the organization, if no argument is given, the organization currently in use will be shown.", + Use: "show [\"selected\"|\"me\"|uuid|org_name]", + Short: "Show the organization. " + + "Using \"selected\" will show the selected organization from the \"--org\" flag. " + + "Using \"me\" will show all organizations you are a member of.", + Long: FormatExamples( + Example{ + Description: "coder org show selected", + Command: "Shows the organizations selected with '--org='. " + + "This organization is the organization used by the cli.", + }, + Example{ + Description: "coder org show me", + Command: "List of all organizations you are a member of.", + }, + Example{ + Description: "coder org show developers", + Command: "Show organization with name 'developers'", + }, + Example{ + Description: "coder org show 90ee1875-3db5-43b3-828e-af3687522e43", + Command: "Show organization with the given ID.", + }, + ), Middleware: serpent.Chain( r.InitClient(client), serpent.RequireRangeArgs(0, 1), @@ -242,7 +90,7 @@ func (r *RootCmd) currentOrganization() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { - orgArg := "current" + orgArg := "selected" if len(inv.Args) >= 1 { orgArg = inv.Args[0] } @@ -250,14 +98,14 @@ func (r *RootCmd) currentOrganization() *serpent.Command { var orgs []codersdk.Organization var err error switch strings.ToLower(orgArg) { - case "current": + case "selected": stringFormat = func(orgs []codersdk.Organization) (string, error) { if len(orgs) != 1 { return "", xerrors.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) + org, err := orgContext.Selected(inv, client) if err != nil { return err } diff --git a/cli/organization_test.go b/cli/organization_test.go index d5a9eeb057bfb..d04ea9cc6a19f 100644 --- a/cli/organization_test.go +++ b/cli/organization_test.go @@ -43,7 +43,7 @@ func TestCurrentOrganization(t *testing.T) { defer srv.Close() client := codersdk.New(must(url.Parse(srv.URL))) - inv, root := clitest.New(t, "organizations", "show", "current") + inv, root := clitest.New(t, "organizations", "show", "selected") clitest.SetupConfig(t, client, root) pty := ptytest.New(t).Attach(inv) errC := make(chan error) @@ -70,7 +70,7 @@ func TestCurrentOrganization(t *testing.T) { require.NoError(t, err) } - inv, root := clitest.New(t, "organizations", "show", "--only-id") + inv, root := clitest.New(t, "organizations", "show", "--only-id", "--org="+first.OrganizationID.String()) clitest.SetupConfig(t, client, root) pty := ptytest.New(t).Attach(inv) errC := make(chan error) @@ -101,7 +101,7 @@ func TestCurrentOrganization(t *testing.T) { orgs[orgName] = org } - inv, root := clitest.New(t, "organizations", "show", "current", "--only-id", "-z=bar") + inv, root := clitest.New(t, "organizations", "show", "selected", "--only-id", "-O=bar") clitest.SetupConfig(t, client, root) pty := ptytest.New(t).Attach(inv) errC := make(chan error) @@ -113,40 +113,6 @@ func TestCurrentOrganization(t *testing.T) { }) } -func TestOrganizationSwitch(t *testing.T) { - t.Parallel() - - t.Run("Switch", 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) - } - - exp, err := client.OrganizationByName(ctx, "foo") - require.NoError(t, err) - - inv, root := clitest.New(t, "organizations", "set", "foo") - 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(exp.ID.String()) - }) -} - func must[V any](v V, err error) V { if err != nil { panic(err) diff --git a/cli/organizationmembers.go b/cli/organizationmembers.go index 521ec5bfb7d37..99b0700c4688d 100644 --- a/cli/organizationmembers.go +++ b/cli/organizationmembers.go @@ -11,16 +11,16 @@ import ( "github.com/coder/serpent" ) -func (r *RootCmd) organizationMembers() *serpent.Command { +func (r *RootCmd) organizationMembers(orgContext *OrganizationContext) *serpent.Command { cmd := &serpent.Command{ Use: "members", Aliases: []string{"member"}, Short: "Manage organization members", Children: []*serpent.Command{ - r.listOrganizationMembers(), - r.assignOrganizationRoles(), - r.addOrganizationMember(), - r.removeOrganizationMember(), + r.listOrganizationMembers(orgContext), + r.assignOrganizationRoles(orgContext), + r.addOrganizationMember(orgContext), + r.removeOrganizationMember(orgContext), }, Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) @@ -30,7 +30,7 @@ func (r *RootCmd) organizationMembers() *serpent.Command { return cmd } -func (r *RootCmd) removeOrganizationMember() *serpent.Command { +func (r *RootCmd) removeOrganizationMember(orgContext *OrganizationContext) *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ @@ -42,7 +42,7 @@ func (r *RootCmd) removeOrganizationMember() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -61,7 +61,7 @@ func (r *RootCmd) removeOrganizationMember() *serpent.Command { return cmd } -func (r *RootCmd) addOrganizationMember() *serpent.Command { +func (r *RootCmd) addOrganizationMember(orgContext *OrganizationContext) *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ @@ -73,7 +73,7 @@ func (r *RootCmd) addOrganizationMember() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -92,7 +92,7 @@ func (r *RootCmd) addOrganizationMember() *serpent.Command { return cmd } -func (r *RootCmd) assignOrganizationRoles() *serpent.Command { +func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ @@ -104,7 +104,7 @@ func (r *RootCmd) assignOrganizationRoles() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -135,7 +135,7 @@ func (r *RootCmd) assignOrganizationRoles() *serpent.Command { return cmd } -func (r *RootCmd) listOrganizationMembers() *serpent.Command { +func (r *RootCmd) listOrganizationMembers(orgContext *OrganizationContext) *serpent.Command { formatter := cliui.NewOutputFormatter( cliui.TableFormat([]codersdk.OrganizationMemberWithName{}, []string{"username", "organization_roles"}), cliui.JSONFormat(), @@ -151,7 +151,7 @@ func (r *RootCmd) listOrganizationMembers() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } diff --git a/cli/organizationmembers_test.go b/cli/organizationmembers_test.go index bb0029d77a98b..55b3071a55a90 100644 --- a/cli/organizationmembers_test.go +++ b/cli/organizationmembers_test.go @@ -56,7 +56,7 @@ func TestAddOrganizationMembers(t *testing.T) { }) require.NoError(t, err, "create another organization") - inv, root := clitest.New(t, "organization", "members", "add", "--organization", otherOrg.ID.String(), user.Username) + inv, root := clitest.New(t, "organization", "members", "add", "-O", otherOrg.ID.String(), user.Username) //nolint:gocritic // must be an owner clitest.SetupConfig(t, ownerClient, root) @@ -86,7 +86,7 @@ func TestRemoveOrganizationMembers(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) - inv, root := clitest.New(t, "organization", "members", "remove", "--organization", owner.OrganizationID.String(), user.Username) + inv, root := clitest.New(t, "organization", "members", "remove", "-O", owner.OrganizationID.String(), user.Username) clitest.SetupConfig(t, orgAdminClient, root) buf := new(bytes.Buffer) @@ -109,7 +109,7 @@ func TestRemoveOrganizationMembers(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) - inv, root := clitest.New(t, "organization", "members", "remove", "--organization", owner.OrganizationID.String(), "random_name") + inv, root := clitest.New(t, "organization", "members", "remove", "-O", owner.OrganizationID.String(), "random_name") clitest.SetupConfig(t, orgAdminClient, root) buf := new(bytes.Buffer) diff --git a/cli/organizationroles.go b/cli/organizationroles.go index 75cf048198b30..70ff90ab5bee1 100644 --- a/cli/organizationroles.go +++ b/cli/organizationroles.go @@ -16,7 +16,7 @@ import ( "github.com/coder/serpent" ) -func (r *RootCmd) organizationRoles() *serpent.Command { +func (r *RootCmd) organizationRoles(orgContext *OrganizationContext) *serpent.Command { cmd := &serpent.Command{ Use: "roles", Short: "Manage organization roles.", @@ -26,14 +26,14 @@ func (r *RootCmd) organizationRoles() *serpent.Command { }, Hidden: true, Children: []*serpent.Command{ - r.showOrganizationRoles(), - r.editOrganizationRole(), + r.showOrganizationRoles(orgContext), + r.editOrganizationRole(orgContext), }, } return cmd } -func (r *RootCmd) showOrganizationRoles() *serpent.Command { +func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpent.Command { formatter := cliui.NewOutputFormatter( cliui.ChangeFormatterData( cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}), @@ -63,7 +63,7 @@ func (r *RootCmd) showOrganizationRoles() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - org, err := CurrentOrganization(r, inv, client) + org, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -100,7 +100,7 @@ func (r *RootCmd) showOrganizationRoles() *serpent.Command { return cmd } -func (r *RootCmd) editOrganizationRole() *serpent.Command { +func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent.Command { formatter := cliui.NewOutputFormatter( cliui.ChangeFormatterData( cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}), @@ -148,7 +148,7 @@ func (r *RootCmd) editOrganizationRole() *serpent.Command { ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - org, err := CurrentOrganization(r, inv, client) + org, err := orgContext.Selected(inv, client) if err != nil { return err } diff --git a/cli/root.go b/cli/root.go index 073486c640744..a58312a287c35 100644 --- a/cli/root.go +++ b/cli/root.go @@ -29,6 +29,7 @@ import ( "golang.org/x/mod/semver" "golang.org/x/xerrors" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/pretty" "github.com/coder/coder/v2/buildinfo" @@ -52,20 +53,19 @@ var ( ) const ( - varURL = "url" - varToken = "token" - varAgentToken = "agent-token" - varAgentTokenFile = "agent-token-file" - varAgentURL = "agent-url" - varHeader = "header" - varHeaderCommand = "header-command" - varNoOpen = "no-open" - varNoVersionCheck = "no-version-warning" - varNoFeatureWarning = "no-feature-warning" - varForceTty = "force-tty" - varVerbose = "verbose" - varOrganizationSelect = "organization" - varDisableDirect = "disable-direct-connections" + varURL = "url" + varToken = "token" + varAgentToken = "agent-token" + varAgentTokenFile = "agent-token-file" + varAgentURL = "agent-url" + varHeader = "header" + varHeaderCommand = "header-command" + varNoOpen = "no-open" + varNoVersionCheck = "no-version-warning" + varNoFeatureWarning = "no-feature-warning" + varForceTty = "force-tty" + varVerbose = "verbose" + varDisableDirect = "disable-direct-connections" notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." @@ -451,15 +451,6 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err Value: serpent.StringOf(&r.globalConfig), Group: globalGroup, }, - { - Flag: varOrganizationSelect, - FlagShorthand: "z", - Env: "CODER_ORGANIZATION", - Description: "Select which organization (uuid or name) to use This overrides what is present in the config file.", - Value: serpent.StringOf(&r.organizationSelect), - Hidden: true, - Group: globalGroup, - }, { Flag: "version", // This was requested by a customer to assist with their migration. @@ -476,21 +467,20 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err // RootCmd contains parameters and helpers useful to all commands. type RootCmd struct { - clientURL *url.URL - token string - globalConfig string - header []string - headerCommand string - agentToken string - agentTokenFile string - agentURL *url.URL - forceTTY bool - noOpen bool - verbose bool - organizationSelect string - versionFlag bool - disableDirect bool - debugHTTP bool + clientURL *url.URL + token string + globalConfig string + header []string + headerCommand string + agentToken string + agentTokenFile string + agentURL *url.URL + forceTTY bool + noOpen bool + verbose bool + versionFlag bool + disableDirect bool + debugHTTP bool noVersionCheck bool noFeatureWarning bool @@ -632,52 +622,58 @@ func (r *RootCmd) createAgentClient() (*agentsdk.Client, error) { return client, nil } -// CurrentOrganization returns the currently active organization for the authenticated user. -func CurrentOrganization(r *RootCmd, inv *serpent.Invocation, client *codersdk.Client) (codersdk.Organization, error) { - conf := r.createConfig() - selected := r.organizationSelect - if selected == "" && conf.Organization().Exists() { - org, err := conf.Organization().Read() - if err != nil { - return codersdk.Organization{}, xerrors.Errorf("read selected organization from config file %q: %w", conf.Organization(), err) - } - selected = org - } +type OrganizationContext struct { + // FlagSelect is the value passed in via the --org flag + FlagSelect string +} - // Verify the org exists and the user is a member +func NewOrganizationContext() *OrganizationContext { + return &OrganizationContext{} +} + +func (o *OrganizationContext) AttachOptions(cmd *serpent.Command) { + cmd.Options = append(cmd.Options, serpent.Option{ + Name: "Organization", + Description: "Select which organization (uuid or name) to use.", + // Only required if the user is a part of more than 1 organization. + // Otherwise, we can assume a default value. + Required: false, + Flag: "org", + FlagShorthand: "O", + Env: "CODER_ORGANIZATION", + Value: serpent.StringOf(&o.FlagSelect), + }) +} + +func (o *OrganizationContext) Selected(inv *serpent.Invocation, client *codersdk.Client) (codersdk.Organization, error) { + // Fetch the set of organizations the user is a member of. orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me) if err != nil { - return codersdk.Organization{}, err + return codersdk.Organization{}, xerrors.Errorf("get organizations: %w", err) } // User manually selected an organization - if selected != "" { + if o.FlagSelect != "" { index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool { - return org.Name == selected || org.ID.String() == selected + return org.Name == o.FlagSelect || org.ID.String() == o.FlagSelect }) if index < 0 { - return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization? If unsure, run 'coder organizations set \"\" ' to reset your current context.", selected) + names := db2sdk.List(orgs, func(f codersdk.Organization) string { + return f.Name + }) + return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization? "+ + "Valid options for '--org=' are [%s].", o.FlagSelect, strings.Join(names, ", ")) } 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 { - if len(orgs) == 1 { - // If there is no "isDefault", but only 1 org is present. We can just - // assume the single organization is correct. This is mainly a helper - // for cli hitting an old instance, or a user that belongs to a single - // org that is not the default. - return orgs[0], nil - } - return codersdk.Organization{}, xerrors.Errorf("unable to determine current organization. Use 'coder org set ' to select an organization to use") + if len(orgs) == 1 { + return orgs[0], nil } - return orgs[index], nil + // No org selected, and we are more than 1? Return an error. + return codersdk.Organization{}, xerrors.Errorf("Must select an organization with --org=.") } func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) { diff --git a/cli/templatecreate.go b/cli/templatecreate.go index c570a0d60620d..e70a7bba03a8b 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -31,6 +31,7 @@ func (r *RootCmd) templateCreate() *serpent.Command { dormancyAutoDeletion time.Duration uploadFlags templateUploadFlags + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) cmd := &serpent.Command{ @@ -68,7 +69,7 @@ func (r *RootCmd) templateCreate() *serpent.Command { } } - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } diff --git a/cli/templatedelete.go b/cli/templatedelete.go index 7ded11dd8f00a..120693b952eef 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -15,6 +15,7 @@ import ( ) func (r *RootCmd) templateDelete() *serpent.Command { + orgContext := NewOrganizationContext() client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete [name...]", @@ -32,7 +33,7 @@ func (r *RootCmd) templateDelete() *serpent.Command { templates = []codersdk.Template{} ) - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -81,6 +82,7 @@ func (r *RootCmd) templateDelete() *serpent.Command { return nil }, } + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templateedit.go b/cli/templateedit.go index fbf740097b86f..4ac9c56f92534 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -36,6 +36,7 @@ func (r *RootCmd) templateEdit() *serpent.Command { requireActiveVersion bool deprecationMessage string disableEveryone bool + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) @@ -77,7 +78,7 @@ func (r *RootCmd) templateEdit() *serpent.Command { } } - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } @@ -324,6 +325,7 @@ func (r *RootCmd) templateEdit() *serpent.Command { }, cliui.SkipPromptOption(), } + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templatelist.go b/cli/templatelist.go index ece2d2703b409..6a866ad3f83e5 100644 --- a/cli/templatelist.go +++ b/cli/templatelist.go @@ -11,6 +11,7 @@ import ( ) func (r *RootCmd) templateList() *serpent.Command { + orgContext := NewOrganizationContext() formatter := cliui.NewOutputFormatter( cliui.TableFormat([]templateTableRow{}, []string{"name", "last updated", "used by"}), cliui.JSONFormat(), @@ -25,7 +26,7 @@ func (r *RootCmd) templateList() *serpent.Command { r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -52,5 +53,6 @@ func (r *RootCmd) templateList() *serpent.Command { } formatter.AttachOptions(&cmd.Options) + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templatepull.go b/cli/templatepull.go index 7f9317be376f6..3170e3cd585ea 100644 --- a/cli/templatepull.go +++ b/cli/templatepull.go @@ -20,6 +20,7 @@ func (r *RootCmd) templatePull() *serpent.Command { tarMode bool zipMode bool versionName string + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) @@ -45,7 +46,7 @@ func (r *RootCmd) templatePull() *serpent.Command { return xerrors.Errorf("either tar or zip can be selected") } - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } @@ -187,6 +188,7 @@ func (r *RootCmd) templatePull() *serpent.Command { }, cliui.SkipPromptOption(), } + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templatepush.go b/cli/templatepush.go index b4ff8e50eb5ed..de02af5c0e0db 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -34,6 +34,7 @@ func (r *RootCmd) templatePush() *serpent.Command { provisionerTags []string uploadFlags templateUploadFlags activate bool + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) cmd := &serpent.Command{ @@ -46,7 +47,7 @@ func (r *RootCmd) templatePush() *serpent.Command { Handler: func(inv *serpent.Invocation) error { uploadFlags.setWorkdir(workdir) - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -226,6 +227,7 @@ func (r *RootCmd) templatePush() *serpent.Command { cliui.SkipPromptOption(), } cmd.Options = append(cmd.Options, uploadFlags.options()...) + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templateversionarchive.go b/cli/templateversionarchive.go index f9ae87e330be0..10beda42b9afa 100644 --- a/cli/templateversionarchive.go +++ b/cli/templateversionarchive.go @@ -31,6 +31,7 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { pastVerb = "unarchived" } + orgContext := NewOrganizationContext() client := new(codersdk.Client) cmd := &serpent.Command{ Use: presentVerb + " [template-version-names...] ", @@ -47,7 +48,7 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { versions []codersdk.TemplateVersion ) - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -92,6 +93,7 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { return nil }, } + orgContext.AttachOptions(cmd) return cmd } @@ -99,6 +101,7 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { func (r *RootCmd) archiveTemplateVersions() *serpent.Command { var all serpent.Bool client := new(codersdk.Client) + orgContext := NewOrganizationContext() cmd := &serpent.Command{ Use: "archive [template-name...] ", Short: "Archive unused or failed template versions from a given template(s)", @@ -121,7 +124,7 @@ func (r *RootCmd) archiveTemplateVersions() *serpent.Command { templates = []codersdk.Template{} ) - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -179,6 +182,7 @@ func (r *RootCmd) archiveTemplateVersions() *serpent.Command { return nil }, } + orgContext.AttachOptions(cmd) return cmd } diff --git a/cli/templateversions.go b/cli/templateversions.go index 4460c3b5bfee5..9154e6724291d 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -51,6 +51,7 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { cliui.JSONFormat(), ) client := new(codersdk.Client) + orgContext := NewOrganizationContext() var includeArchived serpent.Bool @@ -93,7 +94,7 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) } @@ -122,6 +123,7 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { }, } + orgContext.AttachOptions(cmd) formatter.AttachOptions(&cmd.Options) return cmd } diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index 9edadd550012d..7101eec667d0a 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -10,6 +10,9 @@ USAGE: $ coder create / OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + --automatic-updates string, $CODER_WORKSPACE_AUTOMATIC_UPDATES (default: never) Specify automatic updates setting for the workspace (accepts 'always' or 'never'). diff --git a/cli/testdata/coder_templates_archive_--help.golden b/cli/testdata/coder_templates_archive_--help.golden index ad9778ad9990c..ebad38db93341 100644 --- a/cli/testdata/coder_templates_archive_--help.golden +++ b/cli/testdata/coder_templates_archive_--help.golden @@ -6,6 +6,9 @@ USAGE: Archive unused or failed template versions from a given template(s) OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + --all bool Include all unused template versions. By default, only failed template versions are archived. diff --git a/cli/testdata/coder_templates_delete_--help.golden b/cli/testdata/coder_templates_delete_--help.golden index 2ba706b7d2aab..4d15b7f34382b 100644 --- a/cli/testdata/coder_templates_delete_--help.golden +++ b/cli/testdata/coder_templates_delete_--help.golden @@ -8,6 +8,9 @@ USAGE: Aliases: rm OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -y, --yes bool Bypass prompts. diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden index 29184b969bf44..6c33faa3d9c3b 100644 --- a/cli/testdata/coder_templates_edit_--help.golden +++ b/cli/testdata/coder_templates_edit_--help.golden @@ -6,6 +6,9 @@ USAGE: Edit the metadata of a template by name. OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + --activity-bump duration Edit the template activity bump - workspaces created from this template will have their shutdown time bumped by this value when diff --git a/cli/testdata/coder_templates_list_--help.golden b/cli/testdata/coder_templates_list_--help.golden index c76905cae27f4..3522902eaa75f 100644 --- a/cli/testdata/coder_templates_list_--help.golden +++ b/cli/testdata/coder_templates_list_--help.golden @@ -8,6 +8,9 @@ USAGE: Aliases: ls OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -c, --column string-array (default: name,last updated,used by) Columns to display in table output. Available columns: name, created at, last updated, organization id, provisioner, active version id, diff --git a/cli/testdata/coder_templates_pull_--help.golden b/cli/testdata/coder_templates_pull_--help.golden index 2598e35a303ef..3a04c351f1f86 100644 --- a/cli/testdata/coder_templates_pull_--help.golden +++ b/cli/testdata/coder_templates_pull_--help.golden @@ -6,6 +6,9 @@ USAGE: Download the active, latest, or specified version of a template to a path. OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + --tar bool Output the template as a tar archive to stdout. diff --git a/cli/testdata/coder_templates_push_--help.golden b/cli/testdata/coder_templates_push_--help.golden index 092e16f897bee..eee0ad34ca925 100644 --- a/cli/testdata/coder_templates_push_--help.golden +++ b/cli/testdata/coder_templates_push_--help.golden @@ -6,6 +6,9 @@ USAGE: Create or update a template from the current directory or as specified by flag OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + --activate bool (default: true) Whether the new template will be marked active. diff --git a/cli/testdata/coder_templates_versions_archive_--help.golden b/cli/testdata/coder_templates_versions_archive_--help.golden index 463a83cf22a1d..eae5a22ff37d6 100644 --- a/cli/testdata/coder_templates_versions_archive_--help.golden +++ b/cli/testdata/coder_templates_versions_archive_--help.golden @@ -7,6 +7,9 @@ USAGE: Archive a template version(s). OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -y, --yes bool Bypass prompts. diff --git a/cli/testdata/coder_templates_versions_list_--help.golden b/cli/testdata/coder_templates_versions_list_--help.golden index 3646c2dada80e..186f15a3ef9f8 100644 --- a/cli/testdata/coder_templates_versions_list_--help.golden +++ b/cli/testdata/coder_templates_versions_list_--help.golden @@ -6,6 +6,9 @@ USAGE: List all the versions of the specified template OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -c, --column string-array (default: Name,Created At,Created By,Status,Active) Columns to display in table output. Available columns: name, created at, created by, status, active, archived. diff --git a/cli/testdata/coder_templates_versions_unarchive_--help.golden b/cli/testdata/coder_templates_versions_unarchive_--help.golden index e2241b14bc018..6a641929fa20d 100644 --- a/cli/testdata/coder_templates_versions_unarchive_--help.golden +++ b/cli/testdata/coder_templates_versions_unarchive_--help.golden @@ -7,6 +7,9 @@ USAGE: Unarchive a template version(s). OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -y, --yes bool Bypass prompts. diff --git a/cli/testdata/coder_users_create_--help.golden b/cli/testdata/coder_users_create_--help.golden index d55d522181c95..5f57485b52f3c 100644 --- a/cli/testdata/coder_users_create_--help.golden +++ b/cli/testdata/coder_users_create_--help.golden @@ -4,6 +4,9 @@ USAGE: coder users create [flags] OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + -e, --email string Specifies an email address for the new user. diff --git a/cli/usercreate.go b/cli/usercreate.go index 3c4a43b33bc2d..257bb1634f1d8 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -24,6 +24,7 @@ func (r *RootCmd) userCreate() *serpent.Command { password string disableLogin bool loginType string + orgContext = NewOrganizationContext() ) client := new(codersdk.Client) cmd := &serpent.Command{ @@ -33,7 +34,7 @@ func (r *RootCmd) userCreate() *serpent.Command { r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - organization, err := CurrentOrganization(r, inv, client) + organization, err := orgContext.Selected(inv, client) if err != nil { return err } @@ -175,5 +176,7 @@ Create a workspace `+pretty.Sprint(cliui.DefaultStyles.Code, "coder create")+`! Value: serpent.StringOf(&loginType), }, } + + orgContext.AttachOptions(cmd) return cmd } diff --git a/docs/cli/create.md b/docs/cli/create.md index 53f90751513d2..aefaf4d316d0b 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -100,3 +100,12 @@ Specify a file path with values for rich parameters defined in the template. | Environment | $CODER_RICH_PARAMETER_DEFAULT | Rich parameter default values in the format "name=value". + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/groups_create.md b/docs/cli/groups_create.md index dd51ed7233a9a..e758b422ea387 100644 --- a/docs/cli/groups_create.md +++ b/docs/cli/groups_create.md @@ -29,3 +29,12 @@ Set an avatar for a group. | Environment | $CODER_DISPLAY_NAME | Optional human friendly name for the group. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/groups_delete.md b/docs/cli/groups_delete.md index f57faff0b9f59..7bbf215ae2f29 100644 --- a/docs/cli/groups_delete.md +++ b/docs/cli/groups_delete.md @@ -11,5 +11,16 @@ Aliases: ## Usage ```console -coder groups delete +coder groups delete [flags] ``` + +## Options + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/groups_edit.md b/docs/cli/groups_edit.md index 2006ba85abd4d..f7c39c58e1d24 100644 --- a/docs/cli/groups_edit.md +++ b/docs/cli/groups_edit.md @@ -52,3 +52,12 @@ Add users to the group. Accepts emails or IDs. | Type | string-array | Remove users to the group. Accepts emails or IDs. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/groups_list.md b/docs/cli/groups_list.md index 5f9e184f3995d..04d9fe726adfd 100644 --- a/docs/cli/groups_list.md +++ b/docs/cli/groups_list.md @@ -29,3 +29,12 @@ Columns to display in table output. Available columns: name, display name, organ | Default | table | Output format. Available formats: table, json. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_archive.md b/docs/cli/templates_archive.md index 04f6d65927a08..a229222addf88 100644 --- a/docs/cli/templates_archive.md +++ b/docs/cli/templates_archive.md @@ -27,3 +27,12 @@ Bypass prompts. | Type | bool | Include all unused template versions. By default, only failed template versions are archived. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_delete.md b/docs/cli/templates_delete.md index aad8ac207f071..55730c7d609d8 100644 --- a/docs/cli/templates_delete.md +++ b/docs/cli/templates_delete.md @@ -23,3 +23,12 @@ coder templates delete [flags] [name...] | Type | bool | Bypass prompts. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_edit.md b/docs/cli/templates_edit.md index 45851225f129a..0e47a9b9be6bc 100644 --- a/docs/cli/templates_edit.md +++ b/docs/cli/templates_edit.md @@ -171,3 +171,12 @@ Disable the default behavior of granting template access to the 'everyone' group | Type | bool | Bypass prompts. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_list.md b/docs/cli/templates_list.md index 7e418e32c35c2..86f5386d3f043 100644 --- a/docs/cli/templates_list.md +++ b/docs/cli/templates_list.md @@ -33,3 +33,12 @@ Columns to display in table output. Available columns: name, created at, last up | Default | table | Output format. Available formats: table, json. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_pull.md b/docs/cli/templates_pull.md index ab99df094ef30..3678426fd098e 100644 --- a/docs/cli/templates_pull.md +++ b/docs/cli/templates_pull.md @@ -43,3 +43,12 @@ The name of the template version to pull. Use 'active' to pull the active versio | Type | bool | Bypass prompts. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_push.md b/docs/cli/templates_push.md index aea080a28d186..e56528841ebda 100644 --- a/docs/cli/templates_push.md +++ b/docs/cli/templates_push.md @@ -102,3 +102,12 @@ Ignore warnings about not having a .terraform.lock.hcl file present in the templ | Type | string | Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_versions_archive.md b/docs/cli/templates_versions_archive.md index 3921f6d0032b5..d6053db9ca185 100644 --- a/docs/cli/templates_versions_archive.md +++ b/docs/cli/templates_versions_archive.md @@ -19,3 +19,12 @@ coder templates versions archive [flags] [template-version-names | Type | bool | Bypass prompts. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/templates_versions_list.md b/docs/cli/templates_versions_list.md index 2c6544569dcba..ca42bce770515 100644 --- a/docs/cli/templates_versions_list.md +++ b/docs/cli/templates_versions_list.md @@ -20,6 +20,15 @@ coder templates versions list [flags]