Skip to content

Commit 1f5b341

Browse files
authored
feat(autoconnect): try up to 3 non-default profiles #2316
Useful for Cloud9 and other ECS-like envs. Example: 2021-11-18 17:51:08 [INFO]: autoconnect: trying "profile:default" 2021-11-18 17:51:09 [WARN]: autoconnect: failed to connect: 'profile:default' 2021-11-18 17:51:09 [INFO]: autoconnect: trying "profile:sso-test" 2021-11-18 17:51:09 [INFO]: autoconnect: trying "profile:foo-dev" 2021-11-18 17:51:10 [INFO]: autoconnect: connected: 'profile:foo-dev'
1 parent 98119ca commit 1f5b341

File tree

5 files changed

+39
-9
lines changed

5 files changed

+39
-9
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Improve auto-connect reliability"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Toolkit appeared stuck at \"Connecting...\""
4+
}

src/credentials/activation.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,16 @@ export async function initialize(parameters: CredentialsInitializeParameters): P
3434
)
3535
}
3636

37+
/**
38+
* Auto-connects with the last-used credentials, else the "default" profile,
39+
* else randomly tries the first three profiles (for Cloud9, ECS, or other
40+
* container-like environments).
41+
*/
3742
export async function loginWithMostRecentCredentials(
3843
toolkitSettings: SettingsConfiguration,
3944
loginManager: LoginManager
4045
): Promise<void> {
46+
const defaultName = 'profile:default'
4147
const manager = CredentialsProviderManager.getInstance()
4248
const previousCredentialsId = toolkitSettings.readSetting<string>(profileSettingKey, '')
4349

@@ -48,7 +54,7 @@ export async function loginWithMostRecentCredentials(
4854
getLogger().warn('autoconnect: getCredentialsProvider() lookup failed for profile: %O', asString(creds))
4955
} else if (provider.canAutoConnect()) {
5056
if (!(await loginManager.login({ passive: true, providerId: creds }))) {
51-
getLogger().warn('autoconnect: failed to connect: %O', asString(creds))
57+
getLogger().warn('autoconnect: failed to connect: "%s"', asString(creds))
5258
return false
5359
}
5460
getLogger().info('autoconnect: connected: %O', asString(creds))
@@ -77,32 +83,44 @@ export async function loginWithMostRecentCredentials(
7783
if (await tryConnect(loginCredentialsId, false)) {
7884
return
7985
}
80-
getLogger().warn('autoconnect: failed to login "%s"', previousCredentialsId)
86+
getLogger().warn('autoconnect: login failed: "%s"', previousCredentialsId)
8187
}
8288

8389
const providerMap = await manager.getCredentialProviderNames()
8490
const profileNames = Object.keys(providerMap)
8591
// Look for "default" profile or exactly one (any name).
86-
const defaultProfile = profileNames.includes('profile:default')
87-
? 'profile:default'
92+
const defaultProfile = profileNames.includes(defaultName)
93+
? defaultName
8894
: profileNames.length === 1
8995
? profileNames[0]
9096
: undefined
9197

92-
if (!previousCredentialsId && !defaultProfile) {
98+
if (!previousCredentialsId && profileNames.length === 0) {
9399
await loginManager.logout(true)
94100
getLogger().info('autoconnect: skipped (profileNames=%d)', profileNames.length)
95101
return
96102
}
97103

98-
// Auto-connect if there is a default profile.
104+
// Try to auto-connect the default profile.
99105
if (defaultProfile) {
100-
getLogger().debug('autoconnect: trying "%s"', defaultProfile)
106+
getLogger().info('autoconnect: trying "%s"', defaultProfile)
101107
if (await tryConnect(providerMap[defaultProfile], !isCloud9())) {
102108
return
103109
}
104110
}
105111

112+
// Try to auto-connect up to 3 other profiles (useful for Cloud9, ECS, …).
113+
for (let i = 0; i < 4 && i < profileNames.length; i++) {
114+
const p = profileNames[i]
115+
if (p === defaultName) {
116+
continue
117+
}
118+
getLogger().info('autoconnect: trying "%s"', p)
119+
if (await tryConnect(providerMap[p], !isCloud9())) {
120+
return
121+
}
122+
}
123+
106124
await loginManager.logout(true)
107125
}
108126

src/credentials/loginManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import { TimeoutError } from '../shared/utilities/timeoutUtils'
67
import { AwsContext } from '../shared/awsContext'
78
import { getAccountId } from '../shared/credentials/accountId'
89
import { getLogger } from '../shared/logger'
@@ -67,8 +68,7 @@ export class LoginManager {
6768
telemetryResult = 'Succeeded'
6869
return true
6970
} catch (err) {
70-
// TODO: don't hardcode logic using error message, have a 'type' field instead
71-
if (!(err as Error).message.includes('cancel')) {
71+
if (!TimeoutError.isCancelled(err)) {
7272
const msg = `login: failed to connect with "${asString(args.providerId)}": ${(err as Error).message}`
7373
if (!args.passive) {
7474
notifyUserInvalidCredentials(args.providerId)

src/shared/utilities/timeoutUtils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export class TimeoutError extends Error {
1313
public constructor(public readonly type: 'expired' | 'cancelled') {
1414
super(type === 'cancelled' ? TIMEOUT_CANCELLED_MESSAGE : TIMEOUT_EXPIRED_MESSAGE)
1515
}
16+
17+
public static isCancelled(err: any): err is TimeoutError & { type: 'cancelled' } {
18+
return err instanceof TimeoutError && err.type === 'cancelled'
19+
}
1620
}
1721

1822
/**

0 commit comments

Comments
 (0)