Skip to content

Commit 7d7a7cc

Browse files
authored
Enable client lookup of default orgs if not locally set (#510)
1 parent dde8ebd commit 7d7a7cc

File tree

8 files changed

+204
-18
lines changed

8 files changed

+204
-18
lines changed

cmd/esc/cli/cli_test.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ func (env testEnviron) Vars() []string {
151151

152152
type testPulumiWorkspace struct {
153153
credentials workspace.Credentials
154+
config workspace.PulumiConfig
154155
}
155156

156157
func (w *testPulumiWorkspace) DeleteAccount(backendURL string) error {
@@ -167,12 +168,12 @@ func (w *testPulumiWorkspace) DeleteAllAccounts() error {
167168
return nil
168169
}
169170

170-
func (*testPulumiWorkspace) SetBackendConfigDefaultOrg(backendURL, defaultOrg string) error {
171+
func (w *testPulumiWorkspace) SetBackendConfigDefaultOrg(backendURL, defaultOrg string) error {
171172
return nil
172173
}
173174

174-
func (*testPulumiWorkspace) GetPulumiConfig() (workspace.PulumiConfig, error) {
175-
return workspace.PulumiConfig{}, nil
175+
func (w *testPulumiWorkspace) GetPulumiConfig() (workspace.PulumiConfig, error) {
176+
return w.config, nil
176177
}
177178

178179
func (*testPulumiWorkspace) GetPulumiPath(elem ...string) (string, error) {
@@ -281,6 +282,7 @@ func (env *testEnvironment) latest() *testEnvironmentRevision {
281282

282283
type testPulumiClient struct {
283284
user string
285+
defaultOrg string
284286
environments map[string]*testEnvironment
285287
openEnvs map[string]*esc.Environment
286288
}
@@ -472,6 +474,11 @@ func (c *testPulumiClient) GetRevisionNumber(ctx context.Context, orgName, proje
472474

473475
}
474476

477+
func (c *testPulumiClient) GetDefaultOrg(ctx context.Context) (string, error) {
478+
return c.defaultOrg, nil
479+
480+
}
481+
475482
func (c *testPulumiClient) ListEnvironments(
476483
ctx context.Context,
477484
continuationToken string,

cmd/esc/cli/client/apitype.go

+8
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,11 @@ type RetractEnvironmentRevisionRequest struct {
207207
Replacement *int `json:"replacement,omitempty"`
208208
Reason string `json:"reason,omitempty"`
209209
}
210+
211+
// GetDefaultOrganizationResponse returns the backend's opinion of which organization
212+
// to default to for a given user, if a default organization has not been configured.
213+
type GetDefaultOrganizationResponse struct {
214+
// Returns the organization name.
215+
// Can be an empty string, if the user is a member of no organizations
216+
Organization string `json:"gitHubLogin"`
217+
}

cmd/esc/cli/client/client.go

+17
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ type Client interface {
6868
// GetRevisionNumber returns the revision number for version.
6969
GetRevisionNumber(ctx context.Context, orgName, projectName, envName, version string) (int, error)
7070

71+
// GetDefaultOrg returns the organization if the backend has an opinion on what user organization to default to,
72+
// if not configured locally by the user.
73+
GetDefaultOrg(ctx context.Context) (string, error)
74+
7175
// ListEnvironments lists all environments that are accessible to the calling user.
7276
//
7377
// Each call to ListEnvironments returns a page of results and a continuation token. If there are no
@@ -453,6 +457,19 @@ func (pc *client) GetRevisionNumber(ctx context.Context, orgName, projectName, e
453457
return resp.Revision, nil
454458
}
455459

460+
// GetDefaultOrg returns the organization if the backend has an opinion on what user organization to default to,
461+
// if not configured locally by the user.
462+
func (pc *client) GetDefaultOrg(ctx context.Context) (string, error) {
463+
var resp GetDefaultOrganizationResponse
464+
if err := pc.restCall(ctx, http.MethodGet, "/api/user/organizations/default", nil, nil, &resp); err != nil {
465+
if IsNotFound(err) {
466+
return "", nil
467+
}
468+
return "", err
469+
}
470+
return resp.Organization, nil
471+
}
472+
456473
func (pc *client) ListEnvironments(
457474
ctx context.Context,
458475
continuationToken string,

cmd/esc/cli/client/client_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,26 @@ func TestDeleteEnvironmentTags(t *testing.T) {
11241124
assert.ErrorContains(t, err, http.StatusText(http.StatusNotFound))
11251125
})
11261126
}
1127+
1128+
func TestGetDefaultOrg(t *testing.T) {
1129+
t.Run("Not Found", func(t *testing.T) {
1130+
// GIVEN
1131+
client := newTestClient(t, http.MethodGet, "/api/user/organizations/default", func(w http.ResponseWriter, r *http.Request) {
1132+
w.WriteHeader(http.StatusNotFound)
1133+
1134+
err := json.NewEncoder(w).Encode(apitype.ErrorResponse{
1135+
Code: http.StatusNotFound,
1136+
Message: http.StatusText(http.StatusNotFound),
1137+
})
1138+
require.NoError(t, err)
1139+
})
1140+
1141+
// WHEN
1142+
orgName, err := client.GetDefaultOrg(context.Background())
1143+
1144+
// THEN
1145+
// We should gracefully swallow the 404.
1146+
assert.NoError(t, err)
1147+
assert.Empty(t, orgName)
1148+
})
1149+
}

cmd/esc/cli/esc.go

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package cli
44

55
import (
66
"bufio"
7+
"context"
78
"fmt"
89
"io"
910
"os"
@@ -176,3 +177,29 @@ func (esc *escCommand) confirmPrompt(prompt, name string) bool {
176177
line, _ := reader.ReadString('\n')
177178
return strings.TrimSpace(line) == name
178179
}
180+
181+
// Looks up the default org.
182+
// Prefers default org that the user has configured locally in their ~/.pulumi/config.json
183+
// If unset, then it will attempt to make an API call to the backend to determine the service's opinion
184+
// of which user organization should be the default; defaults to individual org otherwise if unset.
185+
func (esc *escCommand) lookupDefaultOrg(ctx context.Context, backendURL, username string) (string, error) {
186+
userConfiguredDefaultOrg, err := esc.workspace.GetBackendConfigDefaultOrg(backendURL, username)
187+
if err != nil {
188+
return "", err
189+
}
190+
if userConfiguredDefaultOrg != "" {
191+
return userConfiguredDefaultOrg, nil
192+
}
193+
194+
if esc.client != nil {
195+
backendDefaultOrg, err := esc.client.GetDefaultOrg(ctx)
196+
if err != nil {
197+
return backendDefaultOrg, err
198+
} else if backendDefaultOrg != "" {
199+
return backendDefaultOrg, err
200+
}
201+
}
202+
203+
// If client is unset, or if neither user nor backend have default configured, return the individual org.
204+
return username, nil
205+
}

cmd/esc/cli/login.go

+9-13
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,8 @@ func (esc *escCommand) getCachedClient(ctx context.Context) error {
172172
return fmt.Errorf("could not determine current cloud: %w", err)
173173
}
174174

175-
defaultOrg, err := esc.workspace.GetBackendConfigDefaultOrg(backendURL, nAccount.Username)
176-
if err != nil {
177-
return fmt.Errorf("could not determine default org: %w", err)
178-
}
179-
180175
account = &workspace.Account{
181-
Account: *nAccount,
182-
DefaultOrg: defaultOrg,
176+
Account: *nAccount,
183177
}
184178
}
185179

@@ -197,6 +191,14 @@ func (esc *escCommand) getCachedClient(ctx context.Context) error {
197191
}
198192

199193
esc.client = esc.newClient(esc.userAgent, account.BackendURL, account.AccessToken, account.Insecure)
194+
195+
defaultOrg, err := esc.lookupDefaultOrg(ctx, backendURL, account.Username)
196+
if err != nil {
197+
return fmt.Errorf("looking up org to default to: %w", err)
198+
} else if defaultOrg != "" {
199+
esc.account.DefaultOrg = defaultOrg
200+
}
201+
200202
return nil
201203
}
202204

@@ -209,15 +211,9 @@ func (esc *escCommand) getCachedCredentials(ctx context.Context, backendURL stri
209211
return false, nil
210212
}
211213

212-
defaultOrg, err := esc.workspace.GetBackendConfigDefaultOrg(backendURL, account.Username)
213-
if err != nil {
214-
return false, err
215-
}
216-
217214
esc.account = workspace.Account{
218215
Account: *account,
219216
BackendURL: backendURL,
220-
DefaultOrg: defaultOrg,
221217
}
222218
return true, nil
223219
}

cmd/esc/cli/login_test.go

+108-1
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,21 @@ func TestEnvVarOverridesAccounts(t *testing.T) {
134134
},
135135
},
136136
}
137+
138+
// Configure a default org to skip mocking a client call for default org
139+
backendConfig := make(map[string]pulumi_workspace.BackendConfig, len(creds.Accounts))
140+
for url := range creds.Accounts {
141+
backendConfig[url] = pulumi_workspace.BackendConfig{DefaultOrg: "test-user-org"}
142+
}
143+
137144
esc := &escCommand{
138145
command: "esc",
139146
login: &testLoginManager{creds: creds},
140147
newClient: func(userAgent, backendURL, accessToken string, insecure bool) client.Client {
141148
return client.New(userAgent, backendURL, accessToken, insecure)
142149
},
143-
workspace: workspace.New(testFS{}, &testPulumiWorkspace{}),
150+
workspace: workspace.New(testFS{}, &testPulumiWorkspace{
151+
config: pulumi_workspace.PulumiConfig{BackendConfig: backendConfig}}),
144152
}
145153

146154
// Verify default
@@ -162,3 +170,102 @@ func TestEnvVarOverridesAccounts(t *testing.T) {
162170
assert.NoError(t, err)
163171
assert.Equal(t, esc.client.URL(), "https://api.pulumi.com")
164172
}
173+
174+
func TestDefaultOrgConfiguration(t *testing.T) {
175+
username := "test-user"
176+
backend := "https://api.pulumi.com"
177+
creds := pulumi_workspace.Credentials{
178+
Current: backend,
179+
Accounts: map[string]pulumi_workspace.Account{
180+
backend: {
181+
Username: username,
182+
AccessToken: "access-token",
183+
},
184+
},
185+
}
186+
187+
t.Run("prefers user configuration", func(t *testing.T) {
188+
// GIVEN
189+
// The user has configured a default org:
190+
userConfiguredDefaultOrg := "my-default-org"
191+
testWorkspace := workspace.New(testFS{}, &testPulumiWorkspace{
192+
config: pulumi_workspace.PulumiConfig{
193+
BackendConfig: map[string]pulumi_workspace.BackendConfig{
194+
backend: {
195+
DefaultOrg: userConfiguredDefaultOrg,
196+
},
197+
},
198+
},
199+
})
200+
201+
testClient := testPulumiClient{}
202+
esc := &escCommand{
203+
command: "esc",
204+
login: &testLoginManager{creds: creds},
205+
newClient: func(userAgent, backendURL, accessToken string, insecure bool) client.Client {
206+
return &testClient
207+
},
208+
workspace: testWorkspace,
209+
}
210+
211+
// WHEN
212+
err := esc.getCachedClient(context.Background())
213+
214+
// THEN
215+
assert.NoError(t, err)
216+
assert.Equal(t, userConfiguredDefaultOrg, esc.account.DefaultOrg)
217+
})
218+
219+
t.Run("falls back to backend client configuration", func(t *testing.T) {
220+
// GIVEN
221+
// The user has not configured a default org:
222+
testWorkspace := workspace.New(testFS{}, &testPulumiWorkspace{})
223+
224+
// But the backend has an opinion on the default org:
225+
serviceDefaultOrg := "service-default-org"
226+
testClient := testPulumiClient{
227+
defaultOrg: serviceDefaultOrg,
228+
}
229+
230+
esc := &escCommand{
231+
command: "esc",
232+
login: &testLoginManager{creds: creds},
233+
newClient: func(userAgent, backendURL, accessToken string, insecure bool) client.Client {
234+
return &testClient
235+
},
236+
workspace: testWorkspace,
237+
}
238+
239+
// WHEN
240+
err := esc.getCachedClient(context.Background())
241+
242+
// THEN
243+
assert.NoError(t, err)
244+
assert.Equal(t, serviceDefaultOrg, esc.account.DefaultOrg)
245+
})
246+
247+
t.Run("falls back to individual org as last resort", func(t *testing.T) {
248+
// GIVEN
249+
// The user has not configured a default org:
250+
testWorkspace := workspace.New(testFS{}, &testPulumiWorkspace{})
251+
252+
// And the service has no opinion:
253+
testClient := testPulumiClient{defaultOrg: ""}
254+
255+
esc := &escCommand{
256+
command: "esc",
257+
login: &testLoginManager{creds: creds},
258+
newClient: func(userAgent, backendURL, accessToken string, insecure bool) client.Client {
259+
return &testClient
260+
},
261+
workspace: testWorkspace,
262+
}
263+
264+
// WHEN
265+
err := esc.getCachedClient(context.Background())
266+
267+
// THEN
268+
assert.NoError(t, err)
269+
assert.Equal(t, username, esc.account.DefaultOrg)
270+
})
271+
}

cmd/esc/cli/workspace/config.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func (w *Workspace) SetBackendConfigDefaultOrg(backendURL, defaultOrg string) er
2323
return w.pulumi.SetBackendConfigDefaultOrg(backendURL, defaultOrg)
2424
}
2525

26+
// Returns the default org as configured in the backend, returning an empty string if unset.
2627
func (w *Workspace) GetBackendConfigDefaultOrg(backendURL, username string) (string, error) {
2728
config, err := w.pulumi.GetPulumiConfig()
2829
if err != nil && !errors.Is(err, fs.ErrNotExist) {
@@ -31,5 +32,5 @@ func (w *Workspace) GetBackendConfigDefaultOrg(backendURL, username string) (str
3132
if cfg, ok := config.BackendConfig[backendURL]; ok && cfg.DefaultOrg != "" {
3233
return cfg.DefaultOrg, nil
3334
}
34-
return username, nil
35+
return "", nil
3536
}

0 commit comments

Comments
 (0)