Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit70ccefc

Browse files
authored
feat: set organization context in coder organizations (#12265)
* feat: add coder organizations set to change org context`coder organizations set <org>`
1 parent748cf4b commit70ccefc

File tree

3 files changed

+216
-3
lines changed

3 files changed

+216
-3
lines changed

‎cli/organization.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package cli
22

33
import (
4+
"errors"
45
"fmt"
6+
"os"
7+
"slices"
58
"strings"
69

710
"github.com/coder/coder/v2/cli/clibase"
811
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/cli/config"
913
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/pretty"
1015
)
1116

1217
func (r*RootCmd)organizations()*clibase.Cmd {
@@ -21,13 +26,183 @@ func (r *RootCmd) organizations() *clibase.Cmd {
2126
},
2227
Children: []*clibase.Cmd{
2328
r.currentOrganization(),
29+
r.switchOrganization(),
2430
},
2531
}
2632

2733
cmd.Options= clibase.OptionSet{}
2834
returncmd
2935
}
3036

37+
func (r*RootCmd)switchOrganization()*clibase.Cmd {
38+
client:=new(codersdk.Client)
39+
40+
cmd:=&clibase.Cmd{
41+
Use:"set <organization name | ID>",
42+
Short:"set the organization used by the CLI. Pass an empty string to reset to the default organization.",
43+
Long:"set the organization used by the CLI. Pass an empty string to reset to the default organization.\n"+formatExamples(
44+
example{
45+
Description:"Remove the current organization and defer to the default.",
46+
Command:"coder organizations set ''",
47+
},
48+
example{
49+
Description:"Switch to a custom organization.",
50+
Command:"coder organizations set my-org",
51+
},
52+
),
53+
Middleware:clibase.Chain(
54+
r.InitClient(client),
55+
clibase.RequireRangeArgs(0,1),
56+
),
57+
Options: clibase.OptionSet{},
58+
Handler:func(inv*clibase.Invocation)error {
59+
conf:=r.createConfig()
60+
orgs,err:=client.OrganizationsByUser(inv.Context(),codersdk.Me)
61+
iferr!=nil {
62+
returnfmt.Errorf("failed to get organizations: %w",err)
63+
}
64+
// Keep the list of orgs sorted
65+
slices.SortFunc(orgs,func(a,b codersdk.Organization)int {
66+
returnstrings.Compare(a.Name,b.Name)
67+
})
68+
69+
varswitchToOrgstring
70+
iflen(inv.Args)==0 {
71+
// Pull switchToOrg from a prompt selector, rather than command line
72+
// args.
73+
switchToOrg,err=promptUserSelectOrg(inv,conf,orgs)
74+
iferr!=nil {
75+
returnerr
76+
}
77+
}else {
78+
switchToOrg=inv.Args[0]
79+
}
80+
81+
// If the user passes an empty string, we want to remove the organization
82+
// from the config file. This will defer to default behavior.
83+
ifswitchToOrg=="" {
84+
err:=conf.Organization().Delete()
85+
iferr!=nil&&!errors.Is(err,os.ErrNotExist) {
86+
returnfmt.Errorf("failed to unset organization: %w",err)
87+
}
88+
_,_=fmt.Fprintf(inv.Stdout,"Organization unset\n")
89+
}else {
90+
// Find the selected org in our list.
91+
index:=slices.IndexFunc(orgs,func(org codersdk.Organization)bool {
92+
returnorg.Name==switchToOrg||org.ID.String()==switchToOrg
93+
})
94+
ifindex<0 {
95+
// Using this error for better error message formatting
96+
err:=&codersdk.Error{
97+
Response: codersdk.Response{
98+
Message:fmt.Sprintf("Organization %q not found. Is the name correct, and are you a member of it?",switchToOrg),
99+
Detail:"Ensure the organization argument is correct and you are a member of it.",
100+
},
101+
Helper:fmt.Sprintf("Valid organizations you can switch to: %s",strings.Join(orgNames(orgs),", ")),
102+
}
103+
returnerr
104+
}
105+
106+
// Always write the uuid to the config file. Names can change.
107+
err:=conf.Organization().Write(orgs[index].ID.String())
108+
iferr!=nil {
109+
returnfmt.Errorf("failed to write organization to config file: %w",err)
110+
}
111+
}
112+
113+
// Verify it worked.
114+
current,err:=CurrentOrganization(r,inv,client)
115+
iferr!=nil {
116+
// An SDK error could be a permission error. So offer the advice to unset the org
117+
// and reset the context.
118+
varsdkError*codersdk.Error
119+
iferrors.As(err,&sdkError) {
120+
ifsdkError.Helper==""&&sdkError.StatusCode()!=500 {
121+
sdkError.Helper=`If this error persists, try unsetting your org with 'coder organizations set ""'`
122+
}
123+
returnsdkError
124+
}
125+
returnfmt.Errorf("failed to get current organization: %w",err)
126+
}
127+
128+
_,_=fmt.Fprintf(inv.Stdout,"Current organization context set to %s (%s)\n",current.Name,current.ID.String())
129+
returnnil
130+
},
131+
}
132+
133+
returncmd
134+
}
135+
136+
// promptUserSelectOrg will prompt the user to select an organization from a list
137+
// of their organizations.
138+
funcpromptUserSelectOrg(inv*clibase.Invocation,conf config.Root,orgs []codersdk.Organization) (string,error) {
139+
// Default choice
140+
vardefaultOrgstring
141+
// Comes from config file
142+
ifconf.Organization().Exists() {
143+
defaultOrg,_=conf.Organization().Read()
144+
}
145+
146+
// No config? Comes from default org in the list
147+
ifdefaultOrg=="" {
148+
defIndex:=slices.IndexFunc(orgs,func(org codersdk.Organization)bool {
149+
returnorg.IsDefault
150+
})
151+
ifdefIndex>=0 {
152+
defaultOrg=orgs[defIndex].Name
153+
}
154+
}
155+
156+
// Defer to first org
157+
ifdefaultOrg==""&&len(orgs)>0 {
158+
defaultOrg=orgs[0].Name
159+
}
160+
161+
// Ensure the `defaultOrg` value is an org name, not a uuid.
162+
// If it is a uuid, change it to the org name.
163+
index:=slices.IndexFunc(orgs,func(org codersdk.Organization)bool {
164+
returnorg.ID.String()==defaultOrg||org.Name==defaultOrg
165+
})
166+
ifindex>=0 {
167+
defaultOrg=orgs[index].Name
168+
}
169+
170+
// deselectOption is the option to delete the organization config file and defer
171+
// to default behavior.
172+
constdeselectOption="[Default]"
173+
ifdefaultOrg=="" {
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 set 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+
iferr!=nil {
186+
return"",err
187+
}
188+
// Deselect is an alias for ""
189+
ifvalue==deselectOption {
190+
value=""
191+
}
192+
193+
returnvalue,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+
funcorgNames(orgs []codersdk.Organization) []string {
199+
names:=make([]string,0,len(orgs))
200+
for_,org:=rangeorgs {
201+
names=append(names,org.Name)
202+
}
203+
returnnames
204+
}
205+
31206
func (r*RootCmd)currentOrganization()*clibase.Cmd {
32207
var (
33208
stringFormatfunc(orgs []codersdk.Organization) (string,error)

‎cli/organization_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,37 @@ func TestCurrentOrganization(t *testing.T) {
7474
pty.ExpectMatch(orgs["bar"].ID.String())
7575
})
7676
}
77+
78+
funcTestOrganizationSwitch(t*testing.T) {
79+
t.Parallel()
80+
81+
t.Run("Switch",func(t*testing.T) {
82+
t.Parallel()
83+
ownerClient:=coderdtest.New(t,nil)
84+
first:=coderdtest.CreateFirstUser(t,ownerClient)
85+
// Owner is required to make orgs
86+
client,_:=coderdtest.CreateAnotherUser(t,ownerClient,first.OrganizationID,rbac.RoleOwner())
87+
88+
ctx:=testutil.Context(t,testutil.WaitMedium)
89+
orgs:= []string{"foo","bar"}
90+
for_,orgName:=rangeorgs {
91+
_,err:=client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
92+
Name:orgName,
93+
})
94+
require.NoError(t,err)
95+
}
96+
97+
exp,err:=client.OrganizationByName(ctx,"foo")
98+
require.NoError(t,err)
99+
100+
inv,root:=clitest.New(t,"organizations","set","foo")
101+
clitest.SetupConfig(t,client,root)
102+
pty:=ptytest.New(t).Attach(inv)
103+
errC:=make(chanerror)
104+
gofunc() {
105+
errC<-inv.Run()
106+
}()
107+
require.NoError(t,<-errC)
108+
pty.ExpectMatch(exp.ID.String())
109+
})
110+
}

‎cli/root.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ func CurrentOrganization(r *RootCmd, inv *clibase.Invocation, client *codersdk.C
743743
returnorg.IsDefault
744744
})
745745
ifindex<0 {
746-
return codersdk.Organization{},xerrors.Errorf("unable to determine current organization. Use 'coderorganizations switch <org>' to select an organization to use")
746+
return codersdk.Organization{},xerrors.Errorf("unable to determine current organization. Use 'coderset <org>' to select an organization to use")
747747
}
748748

749749
returnorgs[index],nil
@@ -1202,8 +1202,12 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin
12021202
funcformatCoderSDKError(fromstring,err*codersdk.Error,opts*formatOpts)string {
12031203
varstr strings.Builder
12041204
ifopts.Verbose {
1205-
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("API request error to\"%s:%s\". Status code %d",err.Method(),err.URL(),err.StatusCode())))
1206-
_,_=str.WriteString("\n")
1205+
// If all these fields are empty, then do not print this information.
1206+
// This can occur if the error is being used outside the api.
1207+
if!(err.Method()==""&&err.URL()==""&&err.StatusCode()==0) {
1208+
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("API request error to\"%s:%s\". Status code %d",err.Method(),err.URL(),err.StatusCode())))
1209+
_,_=str.WriteString("\n")
1210+
}
12071211
}
12081212
// Always include this trace. Users can ignore this.
12091213
iffrom!="" {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp