|
9 | 9 |
|
10 | 10 | "github.com/coder/coder/v2/cli/clibase"
|
11 | 11 | "github.com/coder/coder/v2/cli/cliui"
|
| 12 | +"github.com/coder/coder/v2/cli/config" |
12 | 13 | "github.com/coder/coder/v2/codersdk"
|
| 14 | +"github.com/coder/pretty" |
13 | 15 | )
|
14 | 16 |
|
15 | 17 | func (r*RootCmd)organizations()*clibase.Cmd {
|
@@ -52,56 +54,155 @@ func (r *RootCmd) switchOrganization() *clibase.Cmd {
|
52 | 54 | ),
|
53 | 55 | Middleware:clibase.Chain(
|
54 | 56 | r.InitClient(client),
|
| 57 | +clibase.RequireRangeArgs(0,1), |
55 | 58 | ),
|
56 | 59 | Options: clibase.OptionSet{},
|
57 | 60 | Handler:func(inv*clibase.Invocation)error {
|
58 |
| -orgArg:=inv.Args[0] |
59 | 61 | conf:=r.createConfig()
|
60 | 62 | orgs,err:=client.OrganizationsByUser(inv.Context(),codersdk.Me)
|
61 | 63 | iferr!=nil {
|
62 | 64 | returnfmt.Errorf("failed to get organizations: %w",err)
|
63 | 65 | }
|
| 66 | +// Keep the list of orgs sorted |
| 67 | +slices.SortFunc(orgs,func(a,b codersdk.Organization)int { |
| 68 | +returnstrings.Compare(a.Name,b.Name) |
| 69 | +}) |
| 70 | + |
| 71 | +varorgArgstring |
| 72 | +iflen(inv.Args)==0 { |
| 73 | +// Pull orgArg from a prompt selector, rather than command line |
| 74 | +// args. |
| 75 | +orgArg,err=promptUserSelectOrg(inv,conf,orgs) |
| 76 | +iferr!=nil { |
| 77 | +returnerr |
| 78 | +} |
| 79 | +}else { |
| 80 | +orgArg=inv.Args[0] |
| 81 | +} |
64 | 82 |
|
65 | 83 | // If the user passes an empty string, we want to remove the organization
|
| 84 | +// from the config file. This will defer to default behavior. |
66 | 85 | iforgArg=="" {
|
67 | 86 | err:=conf.Organization().Delete()
|
68 | 87 | iferr!=nil&&!errors.Is(err,os.ErrNotExist) {
|
69 | 88 | returnfmt.Errorf("failed to unset organization: %w",err)
|
70 | 89 | }
|
| 90 | +_,_=fmt.Fprintf(inv.Stdout,"Organization unset\n") |
71 | 91 | }else {
|
| 92 | +// Find the selected org in our list. |
72 | 93 | index:=slices.IndexFunc(orgs,func(org codersdk.Organization)bool {
|
73 | 94 | returnorg.Name==orgArg||org.ID.String()==orgArg
|
74 | 95 | })
|
75 | 96 | ifindex<0 {
|
76 |
| -names:=make([]string,0,len(orgs)) |
77 |
| -for_,org:=rangeorgs { |
78 |
| -names=append(names,org.Name) |
79 |
| -} |
80 |
| - |
81 | 97 | // Using this error for better error message formatting
|
82 | 98 | err:=&codersdk.Error{
|
83 | 99 | 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), |
85 | 101 | Detail:"Ensure the organization argument is correct and you are a member of it.",
|
86 | 102 | },
|
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),", ")), |
88 | 104 | }
|
89 | 105 | returnerr
|
90 | 106 | }
|
91 | 107 |
|
| 108 | +// Always write the uuid to the config file. Names can change. |
92 | 109 | err:=conf.Organization().Write(orgs[index].ID.String())
|
93 | 110 | iferr!=nil {
|
94 | 111 | returnfmt.Errorf("failed to write organization to config file: %w",err)
|
95 | 112 | }
|
96 | 113 | }
|
97 | 114 |
|
| 115 | +// Verify it worked. |
| 116 | +current,err:=CurrentOrganization(r,inv,client) |
| 117 | +iferr!=nil { |
| 118 | +// An SDK error could be a permission error. So offer the advice to unset the org |
| 119 | +// and reset the context. |
| 120 | +varsdkError*codersdk.Error |
| 121 | +iferrors.As(err,&sdkError) { |
| 122 | +ifsdkError.Helper==""&&sdkError.StatusCode()!=500 { |
| 123 | +sdkError.Helper=fmt.Sprintf("If this error persists, try unsetting your org with 'coder organizations switch\"\"'") |
| 124 | +} |
| 125 | +returnsdkError |
| 126 | +} |
| 127 | +returnfmt.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()) |
98 | 131 | returnnil
|
99 | 132 | },
|
100 | 133 | }
|
101 | 134 |
|
102 | 135 | returncmd
|
103 | 136 | }
|
104 | 137 |
|
| 138 | +// promptUserSelectOrg will prompt the user to select an organization from a list |
| 139 | +// of their organizations. |
| 140 | +funcpromptUserSelectOrg(inv*clibase.Invocation,conf config.Root,orgs []codersdk.Organization) (string,error) { |
| 141 | +// Default choice |
| 142 | +vardefaultOrgstring |
| 143 | +// Comes from config file |
| 144 | +ifconf.Organization().Exists() { |
| 145 | +defaultOrg,_=conf.Organization().Read() |
| 146 | +} |
| 147 | + |
| 148 | +// No config? Comes from default org in the list |
| 149 | +ifdefaultOrg=="" { |
| 150 | +defIndex:=slices.IndexFunc(orgs,func(org codersdk.Organization)bool { |
| 151 | +returnorg.IsDefault |
| 152 | +}) |
| 153 | +ifdefIndex>=0 { |
| 154 | +defaultOrg=orgs[defIndex].Name |
| 155 | +} |
| 156 | +} |
| 157 | + |
| 158 | +// Defer to first org |
| 159 | +ifdefaultOrg==""&&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 | +returnorg.ID.String()==defaultOrg||org.Name==defaultOrg |
| 167 | +}) |
| 168 | +ifindex>=0 { |
| 169 | +defaultOrg=orgs[index].Name |
| 170 | +} |
| 171 | + |
| 172 | +constdeselectOption="--Deselect--" |
| 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 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 | +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 | + |
105 | 206 | func (r*RootCmd)currentOrganization()*clibase.Cmd {
|
106 | 207 | var (
|
107 | 208 | client=new(codersdk.Client)
|
|