Skip to content

Commit 1f3645e

Browse files
committed
Merge branch 'main' into update-xterm
2 parents 896eb7d + 5bf46f3 commit 1f3645e

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)