Skip to content

Commit 5bf46f3

Browse files
authored
chore: remove org context switcher in the cli (#13674)
* chore: remove org context switcher in the cli
1 parent 4a0fd74 commit 5bf46f3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+353
-362
lines changed

cli/create.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func (r *RootCmd) create() *serpent.Command {
2929
parameterFlags workspaceParameterFlags
3030
autoUpdates string
3131
copyParametersFrom string
32+
orgContext = NewOrganizationContext()
3233
)
3334
client := new(codersdk.Client)
3435
cmd := &serpent.Command{
@@ -43,7 +44,7 @@ func (r *RootCmd) create() *serpent.Command {
4344
),
4445
Middleware: serpent.Chain(r.InitClient(client)),
4546
Handler: func(inv *serpent.Invocation) error {
46-
organization, err := CurrentOrganization(r, inv, client)
47+
organization, err := orgContext.Selected(inv, client)
4748
if err != nil {
4849
return err
4950
}
@@ -269,6 +270,7 @@ func (r *RootCmd) create() *serpent.Command {
269270
)
270271
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
271272
cmd.Options = append(cmd.Options, parameterFlags.cliParameterDefaults()...)
273+
orgContext.AttachOptions(cmd)
272274
return cmd
273275
}
274276

cli/login.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -358,13 +358,6 @@ func (r *RootCmd) login() *serpent.Command {
358358
return xerrors.Errorf("write server url: %w", err)
359359
}
360360

361-
// If the current organization cannot be fetched, then reset the organization context.
362-
// Otherwise, organization cli commands will fail.
363-
_, err = CurrentOrganization(r, inv, client)
364-
if err != nil {
365-
_ = config.Organization().Delete()
366-
}
367-
368361
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n", pretty.Sprint(cliui.DefaultStyles.Keyword, resp.Username))
369362
return nil
370363
},

cli/login_test.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ import (
55
"fmt"
66
"net/http"
77
"net/http/httptest"
8-
"os"
98
"runtime"
109
"testing"
1110

12-
"github.com/google/uuid"
1311
"github.com/stretchr/testify/assert"
1412
"github.com/stretchr/testify/require"
1513

@@ -424,29 +422,6 @@ func TestLogin(t *testing.T) {
424422
require.NotEqual(t, client.SessionToken(), sessionFile)
425423
})
426424

427-
// Login should reset the configured organization if the user is not a member
428-
t.Run("ResetOrganization", func(t *testing.T) {
429-
t.Parallel()
430-
client := coderdtest.New(t, nil)
431-
coderdtest.CreateFirstUser(t, client)
432-
root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken())
433-
434-
notRealOrg := uuid.NewString()
435-
err := cfg.Organization().Write(notRealOrg)
436-
require.NoError(t, err, "write bad org to config")
437-
438-
err = root.Run()
439-
require.NoError(t, err)
440-
sessionFile, err := cfg.Session().Read()
441-
require.NoError(t, err)
442-
require.NotEqual(t, client.SessionToken(), sessionFile)
443-
444-
// Organization config should be deleted since the org does not exist
445-
selected, err := cfg.Organization().Read()
446-
require.ErrorIs(t, err, os.ErrNotExist)
447-
require.NotEqual(t, selected, notRealOrg)
448-
})
449-
450425
t.Run("KeepOrganizationContext", func(t *testing.T) {
451426
t.Parallel()
452427
client := coderdtest.New(t, nil)

cli/organization.go

Lines changed: 33 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
package cli
22

33
import (
4-
"errors"
54
"fmt"
6-
"os"
7-
"slices"
85
"strings"
96

107
"golang.org/x/xerrors"
118

129
"github.com/coder/coder/v2/cli/cliui"
13-
"github.com/coder/coder/v2/cli/config"
1410
"github.com/coder/coder/v2/codersdk"
15-
"github.com/coder/pretty"
1611
"github.com/coder/serpent"
1712
)
1813

1914
func (r *RootCmd) organizations() *serpent.Command {
15+
orgContext := NewOrganizationContext()
16+
2017
cmd := &serpent.Command{
2118
Use: "organizations [subcommand]",
2219
Short: "Organization related commands",
@@ -26,188 +23,18 @@ func (r *RootCmd) organizations() *serpent.Command {
2623
return inv.Command.HelpHandler(inv)
2724
},
2825
Children: []*serpent.Command{
29-
r.currentOrganization(),
30-
r.switchOrganization(),
26+
r.showOrganization(orgContext),
3127
r.createOrganization(),
32-
r.organizationMembers(),
33-
r.organizationRoles(),
28+
r.organizationMembers(orgContext),
29+
r.organizationRoles(orgContext),
3430
},
3531
}
3632

37-
cmd.Options = serpent.OptionSet{}
33+
orgContext.AttachOptions(cmd)
3834
return cmd
3935
}
4036

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 {
21138
var (
21239
stringFormat func(orgs []codersdk.Organization) (string, error)
21340
client = new(codersdk.Client)
@@ -226,8 +53,29 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
22653
onlyID = false
22754
)
22855
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+
),
23179
Middleware: serpent.Chain(
23280
r.InitClient(client),
23381
serpent.RequireRangeArgs(0, 1),
@@ -242,22 +90,22 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
24290
},
24391
},
24492
Handler: func(inv *serpent.Invocation) error {
245-
orgArg := "current"
93+
orgArg := "selected"
24694
if len(inv.Args) >= 1 {
24795
orgArg = inv.Args[0]
24896
}
24997

25098
var orgs []codersdk.Organization
25199
var err error
252100
switch strings.ToLower(orgArg) {
253-
case "current":
101+
case "selected":
254102
stringFormat = func(orgs []codersdk.Organization) (string, error) {
255103
if len(orgs) != 1 {
256104
return "", xerrors.Errorf("expected 1 organization, got %d", len(orgs))
257105
}
258106
return fmt.Sprintf("Current CLI Organization: %s (%s)\n", orgs[0].Name, orgs[0].ID.String()), nil
259107
}
260-
org, err := CurrentOrganization(r, inv, client)
108+
org, err := orgContext.Selected(inv, client)
261109
if err != nil {
262110
return err
263111
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy