Skip to content

Commit 7142630

Browse files
committed
include user prompt if no org specified
1 parent 6a1bb58 commit 7142630

File tree

1 file changed

+109
-8
lines changed

1 file changed

+109
-8
lines changed

cli/organization.go

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99

1010
"github.com/coder/coder/v2/cli/clibase"
1111
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/cli/config"
1213
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/pretty"
1315
)
1416

1517
func (r *RootCmd) organizations() *clibase.Cmd {
@@ -52,56 +54,155 @@ func (r *RootCmd) switchOrganization() *clibase.Cmd {
5254
),
5355
Middleware: clibase.Chain(
5456
r.InitClient(client),
57+
clibase.RequireRangeArgs(0, 1),
5558
),
5659
Options: clibase.OptionSet{},
5760
Handler: func(inv *clibase.Invocation) error {
58-
orgArg := inv.Args[0]
5961
conf := r.createConfig()
6062
orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me)
6163
if err != nil {
6264
return fmt.Errorf("failed to get organizations: %w", err)
6365
}
66+
// Keep the list of orgs sorted
67+
slices.SortFunc(orgs, func(a, b codersdk.Organization) int {
68+
return strings.Compare(a.Name, b.Name)
69+
})
70+
71+
var orgArg string
72+
if len(inv.Args) == 0 {
73+
// Pull orgArg from a prompt selector, rather than command line
74+
// args.
75+
orgArg, err = promptUserSelectOrg(inv, conf, orgs)
76+
if err != nil {
77+
return err
78+
}
79+
} else {
80+
orgArg = inv.Args[0]
81+
}
6482

6583
// If the user passes an empty string, we want to remove the organization
84+
// from the config file. This will defer to default behavior.
6685
if orgArg == "" {
6786
err := conf.Organization().Delete()
6887
if err != nil && !errors.Is(err, os.ErrNotExist) {
6988
return fmt.Errorf("failed to unset organization: %w", err)
7089
}
90+
_, _ = fmt.Fprintf(inv.Stdout, "Organization unset\n")
7191
} else {
92+
// Find the selected org in our list.
7293
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
7394
return org.Name == orgArg || org.ID.String() == orgArg
7495
})
7596
if index < 0 {
76-
names := make([]string, 0, len(orgs))
77-
for _, org := range orgs {
78-
names = append(names, org.Name)
79-
}
80-
8197
// Using this error for better error message formatting
8298
err := &codersdk.Error{
8399
Response: codersdk.Response{
84-
Message: fmt.Sprintf("Organization %q not found.", orgArg),
100+
Message: fmt.Sprintf("Organization %q not found. Is the name correct, and are you a member of it?", orgArg),
85101
Detail: "Ensure the organization argument is correct and you are a member of it.",
86102
},
87-
Helper: fmt.Sprintf("Valid organizations you can switch to: %q", strings.Join(names, ", ")),
103+
Helper: fmt.Sprintf("Valid organizations you can switch to: %q", strings.Join(orgNames(orgs), ", ")),
88104
}
89105
return err
90106
}
91107

108+
// Always write the uuid to the config file. Names can change.
92109
err := conf.Organization().Write(orgs[index].ID.String())
93110
if err != nil {
94111
return fmt.Errorf("failed to write organization to config file: %w", err)
95112
}
96113
}
97114

115+
// Verify it worked.
116+
current, err := CurrentOrganization(r, inv, client)
117+
if err != nil {
118+
// An SDK error could be a permission error. So offer the advice to unset the org
119+
// and reset the context.
120+
var sdkError *codersdk.Error
121+
if errors.As(err, &sdkError) {
122+
if sdkError.Helper == "" && sdkError.StatusCode() != 500 {
123+
sdkError.Helper = fmt.Sprintf("If this error persists, try unsetting your org with 'coder organizations switch \"\"'")
124+
}
125+
return sdkError
126+
}
127+
return fmt.Errorf("failed to get current organization: %w", err)
128+
}
129+
130+
_, _ = fmt.Fprintf(inv.Stdout, "Current organization context set to %s (%s)\n", current.Name, current.ID.String())
98131
return nil
99132
},
100133
}
101134

102135
return cmd
103136
}
104137

138+
// promptUserSelectOrg will prompt the user to select an organization from a list
139+
// of their organizations.
140+
func promptUserSelectOrg(inv *clibase.Invocation, conf config.Root, orgs []codersdk.Organization) (string, error) {
141+
// Default choice
142+
var defaultOrg string
143+
// Comes from config file
144+
if conf.Organization().Exists() {
145+
defaultOrg, _ = conf.Organization().Read()
146+
}
147+
148+
// No config? Comes from default org in the list
149+
if defaultOrg == "" {
150+
defIndex := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
151+
return org.IsDefault
152+
})
153+
if defIndex >= 0 {
154+
defaultOrg = orgs[defIndex].Name
155+
}
156+
}
157+
158+
// Defer to first org
159+
if defaultOrg == "" && len(orgs) > 0 {
160+
defaultOrg = orgs[0].Name
161+
}
162+
163+
// Ensure the `defaultOrg` value is an org name, not a uuid.
164+
// If it is a uuid, change it to the org name.
165+
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
166+
return org.ID.String() == defaultOrg || org.Name == defaultOrg
167+
})
168+
if index >= 0 {
169+
defaultOrg = orgs[index].Name
170+
}
171+
172+
const deselectOption = "--Deselect--"
173+
if defaultOrg == "" {
174+
defaultOrg = deselectOption
175+
}
176+
177+
// Pull value from a prompt
178+
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select an organization below to switch the current cli context to:"))
179+
value, err := cliui.Select(inv, cliui.SelectOptions{
180+
Options: append([]string{deselectOption}, orgNames(orgs)...),
181+
Default: defaultOrg,
182+
Size: 10,
183+
HideSearch: false,
184+
})
185+
if err != nil {
186+
return "", err
187+
}
188+
// Deselect is an alias for ""
189+
if value == deselectOption {
190+
value = ""
191+
}
192+
193+
return value, nil
194+
}
195+
196+
// orgNames is a helper function to turn a list of organizations into a list of
197+
// their names as strings.
198+
func orgNames(orgs []codersdk.Organization) []string {
199+
names := make([]string, 0, len(orgs))
200+
for _, org := range orgs {
201+
names = append(names, org.Name)
202+
}
203+
return names
204+
}
205+
105206
func (r *RootCmd) currentOrganization() *clibase.Cmd {
106207
var (
107208
client = new(codersdk.Client)

0 commit comments

Comments
 (0)