1
1
package cli
2
2
3
3
import (
4
- "errors"
5
4
"fmt"
6
- "os"
7
- "slices"
8
5
"strings"
9
6
10
7
"golang.org/x/xerrors"
11
8
12
9
"github.com/coder/coder/v2/cli/cliui"
13
- "github.com/coder/coder/v2/cli/config"
14
10
"github.com/coder/coder/v2/codersdk"
15
- "github.com/coder/pretty"
16
11
"github.com/coder/serpent"
17
12
)
18
13
19
14
func (r * RootCmd ) organizations () * serpent.Command {
15
+ orgContext := NewOrganizationContext ()
16
+
20
17
cmd := & serpent.Command {
21
18
Use : "organizations [subcommand]" ,
22
19
Short : "Organization related commands" ,
@@ -26,188 +23,18 @@ func (r *RootCmd) organizations() *serpent.Command {
26
23
return inv .Command .HelpHandler (inv )
27
24
},
28
25
Children : []* serpent.Command {
29
- r .currentOrganization (),
30
- r .switchOrganization (),
26
+ r .showOrganization (orgContext ),
31
27
r .createOrganization (),
32
- r .organizationMembers (),
33
- r .organizationRoles (),
28
+ r .organizationMembers (orgContext ),
29
+ r .organizationRoles (orgContext ),
34
30
},
35
31
}
36
32
37
- cmd . Options = serpent. OptionSet {}
33
+ orgContext . AttachOptions ( cmd )
38
34
return cmd
39
35
}
40
36
41
- func (r * RootCmd ) switchOrganization () * serpent.Command {
42
- client := new (codersdk.Client )
43
-
44
- cmd := & serpent.Command {
45
- Use : "set <organization name | ID>" ,
46
- Short : "set the organization used by the CLI. Pass an empty string to reset to the default organization." ,
47
- Long : "set the organization used by the CLI. Pass an empty string to reset to the default organization.\n " + FormatExamples (
48
- Example {
49
- Description : "Remove the current organization and defer to the default." ,
50
- Command : "coder organizations set ''" ,
51
- },
52
- Example {
53
- Description : "Switch to a custom organization." ,
54
- Command : "coder organizations set my-org" ,
55
- },
56
- ),
57
- Middleware : serpent .Chain (
58
- r .InitClient (client ),
59
- serpent .RequireRangeArgs (0 , 1 ),
60
- ),
61
- Options : serpent.OptionSet {},
62
- Handler : func (inv * serpent.Invocation ) error {
63
- conf := r .createConfig ()
64
- orgs , err := client .OrganizationsByUser (inv .Context (), codersdk .Me )
65
- if err != nil {
66
- return xerrors .Errorf ("failed to get organizations: %w" , err )
67
- }
68
- // Keep the list of orgs sorted
69
- slices .SortFunc (orgs , func (a , b codersdk.Organization ) int {
70
- return strings .Compare (a .Name , b .Name )
71
- })
72
-
73
- var switchToOrg string
74
- if len (inv .Args ) == 0 {
75
- // Pull switchToOrg from a prompt selector, rather than command line
76
- // args.
77
- switchToOrg , err = promptUserSelectOrg (inv , conf , orgs )
78
- if err != nil {
79
- return err
80
- }
81
- } else {
82
- switchToOrg = inv .Args [0 ]
83
- }
84
-
85
- // If the user passes an empty string, we want to remove the organization
86
- // from the config file. This will defer to default behavior.
87
- if switchToOrg == "" {
88
- err := conf .Organization ().Delete ()
89
- if err != nil && ! errors .Is (err , os .ErrNotExist ) {
90
- return xerrors .Errorf ("failed to unset organization: %w" , err )
91
- }
92
- _ , _ = fmt .Fprintf (inv .Stdout , "Organization unset\n " )
93
- } else {
94
- // Find the selected org in our list.
95
- index := slices .IndexFunc (orgs , func (org codersdk.Organization ) bool {
96
- return org .Name == switchToOrg || org .ID .String () == switchToOrg
97
- })
98
- if index < 0 {
99
- // Using this error for better error message formatting
100
- err := & codersdk.Error {
101
- Response : codersdk.Response {
102
- Message : fmt .Sprintf ("Organization %q not found. Is the name correct, and are you a member of it?" , switchToOrg ),
103
- Detail : "Ensure the organization argument is correct and you are a member of it." ,
104
- },
105
- Helper : fmt .Sprintf ("Valid organizations you can switch to: %s" , strings .Join (orgNames (orgs ), ", " )),
106
- }
107
- return err
108
- }
109
-
110
- // Always write the uuid to the config file. Names can change.
111
- err := conf .Organization ().Write (orgs [index ].ID .String ())
112
- if err != nil {
113
- return xerrors .Errorf ("failed to write organization to config file: %w" , err )
114
- }
115
- }
116
-
117
- // Verify it worked.
118
- current , err := CurrentOrganization (r , inv , client )
119
- if err != nil {
120
- // An SDK error could be a permission error. So offer the advice to unset the org
121
- // and reset the context.
122
- var sdkError * codersdk.Error
123
- if errors .As (err , & sdkError ) {
124
- if sdkError .Helper == "" && sdkError .StatusCode () != 500 {
125
- sdkError .Helper = `If this error persists, try unsetting your org with 'coder organizations set ""'`
126
- }
127
- return sdkError
128
- }
129
- return xerrors .Errorf ("failed to get current organization: %w" , err )
130
- }
131
-
132
- _ , _ = fmt .Fprintf (inv .Stdout , "Current organization context set to %s (%s)\n " , current .Name , current .ID .String ())
133
- return nil
134
- },
135
- }
136
-
137
- return cmd
138
- }
139
-
140
- // promptUserSelectOrg will prompt the user to select an organization from a list
141
- // of their organizations.
142
- func promptUserSelectOrg (inv * serpent.Invocation , conf config.Root , orgs []codersdk.Organization ) (string , error ) {
143
- // Default choice
144
- var defaultOrg string
145
- // Comes from config file
146
- if conf .Organization ().Exists () {
147
- defaultOrg , _ = conf .Organization ().Read ()
148
- }
149
-
150
- // No config? Comes from default org in the list
151
- if defaultOrg == "" {
152
- defIndex := slices .IndexFunc (orgs , func (org codersdk.Organization ) bool {
153
- return org .IsDefault
154
- })
155
- if defIndex >= 0 {
156
- defaultOrg = orgs [defIndex ].Name
157
- }
158
- }
159
-
160
- // Defer to first org
161
- if defaultOrg == "" && len (orgs ) > 0 {
162
- defaultOrg = orgs [0 ].Name
163
- }
164
-
165
- // Ensure the `defaultOrg` value is an org name, not a uuid.
166
- // If it is a uuid, change it to the org name.
167
- index := slices .IndexFunc (orgs , func (org codersdk.Organization ) bool {
168
- return org .ID .String () == defaultOrg || org .Name == defaultOrg
169
- })
170
- if index >= 0 {
171
- defaultOrg = orgs [index ].Name
172
- }
173
-
174
- // deselectOption is the option to delete the organization config file and defer
175
- // to default behavior.
176
- const deselectOption = "[Default]"
177
- if defaultOrg == "" {
178
- defaultOrg = deselectOption
179
- }
180
-
181
- // Pull value from a prompt
182
- _ , _ = fmt .Fprintln (inv .Stdout , pretty .Sprint (cliui .DefaultStyles .Wrap , "Select an organization below to set the current CLI context to:" ))
183
- value , err := cliui .Select (inv , cliui.SelectOptions {
184
- Options : append ([]string {deselectOption }, orgNames (orgs )... ),
185
- Default : defaultOrg ,
186
- Size : 10 ,
187
- HideSearch : false ,
188
- })
189
- if err != nil {
190
- return "" , err
191
- }
192
- // Deselect is an alias for ""
193
- if value == deselectOption {
194
- value = ""
195
- }
196
-
197
- return value , nil
198
- }
199
-
200
- // orgNames is a helper function to turn a list of organizations into a list of
201
- // their names as strings.
202
- func orgNames (orgs []codersdk.Organization ) []string {
203
- names := make ([]string , 0 , len (orgs ))
204
- for _ , org := range orgs {
205
- names = append (names , org .Name )
206
- }
207
- return names
208
- }
209
-
210
- func (r * RootCmd ) currentOrganization () * serpent.Command {
37
+ func (r * RootCmd ) showOrganization (orgContext * OrganizationContext ) * serpent.Command {
211
38
var (
212
39
stringFormat func (orgs []codersdk.Organization ) (string , error )
213
40
client = new (codersdk.Client )
@@ -226,8 +53,29 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
226
53
onlyID = false
227
54
)
228
55
cmd := & serpent.Command {
229
- Use : "show [current|me|uuid]" ,
230
- Short : "Show the organization, if no argument is given, the organization currently in use will be shown." ,
56
+ Use : "show [\" selected\" |\" me\" |uuid|org_name]" ,
57
+ Short : "Show the organization. " +
58
+ "Using \" selected\" will show the selected organization from the \" --org\" flag. " +
59
+ "Using \" me\" will show all organizations you are a member of." ,
60
+ Long : FormatExamples (
61
+ Example {
62
+ Description : "coder org show selected" ,
63
+ Command : "Shows the organizations selected with '--org=<org_name>'. " +
64
+ "This organization is the organization used by the cli." ,
65
+ },
66
+ Example {
67
+ Description : "coder org show me" ,
68
+ Command : "List of all organizations you are a member of." ,
69
+ },
70
+ Example {
71
+ Description : "coder org show developers" ,
72
+ Command : "Show organization with name 'developers'" ,
73
+ },
74
+ Example {
75
+ Description : "coder org show 90ee1875-3db5-43b3-828e-af3687522e43" ,
76
+ Command : "Show organization with the given ID." ,
77
+ },
78
+ ),
231
79
Middleware : serpent .Chain (
232
80
r .InitClient (client ),
233
81
serpent .RequireRangeArgs (0 , 1 ),
@@ -242,22 +90,22 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
242
90
},
243
91
},
244
92
Handler : func (inv * serpent.Invocation ) error {
245
- orgArg := "current "
93
+ orgArg := "selected "
246
94
if len (inv .Args ) >= 1 {
247
95
orgArg = inv .Args [0 ]
248
96
}
249
97
250
98
var orgs []codersdk.Organization
251
99
var err error
252
100
switch strings .ToLower (orgArg ) {
253
- case "current " :
101
+ case "selected " :
254
102
stringFormat = func (orgs []codersdk.Organization ) (string , error ) {
255
103
if len (orgs ) != 1 {
256
104
return "" , xerrors .Errorf ("expected 1 organization, got %d" , len (orgs ))
257
105
}
258
106
return fmt .Sprintf ("Current CLI Organization: %s (%s)\n " , orgs [0 ].Name , orgs [0 ].ID .String ()), nil
259
107
}
260
- org , err := CurrentOrganization ( r , inv , client )
108
+ org , err := orgContext . Selected ( inv , client )
261
109
if err != nil {
262
110
return err
263
111
}
0 commit comments