Skip to content

Commit a90299d

Browse files
Merge master into feature/v2-to-v3-migration
2 parents 3baa481 + a2849d0 commit a90299d

File tree

12 files changed

+271
-148
lines changed

12 files changed

+271
-148
lines changed

packages/amazonq/package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,6 @@
148148
"markdownDescription": "%AWS.configuration.description.amazonq%",
149149
"default": true
150150
},
151-
"amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {
152-
"markdownDescription": "%AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests%",
153-
"type": "object",
154-
"default": {}
155-
},
156151
"amazonQ.importRecommendationForInlineCodeSuggestions": {
157152
"type": "boolean",
158153
"description": "%AWS.configuration.description.amazonq.importRecommendation%",

packages/core/package.nls.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"AWS.configuration.description.suppressPrompts": "Prompts which ask for confirmation. Checking an item suppresses the prompt.",
2121
"AWS.configuration.enableCodeLenses": "Enable SAM hints in source code and template.yaml files",
2222
"AWS.configuration.description.resources.enabledResources": "AWS resources to display in the 'Resources' portion of the explorer.",
23-
"AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests": "Allow /dev to run code and test commands",
2423
"AWS.configuration.description.experiments": "Try experimental features and give feedback. Note that experimental features may be removed at any time.\n * `jsonResourceModification` - Enables basic create, update, and delete support for cloud resources via the JSON Resources explorer component.",
2524
"AWS.stepFunctions.publishStateMachine.error.invalidYAML": "Cannot publish invalid YAML file",
2625
"AWS.stepFunctions.publishStateMachine.info.creating": "Creating state machine '{0}' in {1}...",

packages/core/src/auth/consoleSessionUtils.ts

Lines changed: 26 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,6 @@ export async function authenticateWithConsoleLogin(profileName?: string, region?
217217

218218
if (result.exitCode === 0) {
219219
telemetry.aws_consoleLoginCLISuccess.emit({ result: 'Succeeded' })
220-
// Show generic success message
221-
void vscode.window.showInformationMessage(
222-
localize(
223-
'AWS.message.success.consoleLogin',
224-
'Login with console credentials command completed. Profile "{0}" is now available.',
225-
profileName
226-
)
227-
)
228220
logger.info('Login with console credentials command completed. Exit code: %d', result.exitCode)
229221
} else if (result.exitCode === 254) {
230222
logger.error(
@@ -291,49 +283,36 @@ export async function authenticateWithConsoleLogin(profileName?: string, region?
291283
code: 'ConsoleLoginConfigError',
292284
})
293285
}
286+
// Show success message after seeing newly created session in the disk
287+
void vscode.window.showInformationMessage(
288+
`Profile "${profileName}" ready with credentials from ${profile.login_session} console session`
289+
)
294290

295-
// Activate the newly created profile
296-
try {
297-
logger.info(`Activating profile: ${profileName}`)
298-
// Connection ID format is "profile:profileName"
299-
const credentialsId: CredentialsId = {
300-
credentialSource: 'profile',
301-
credentialTypeId: profileName,
302-
}
303-
const connectionId = asString(credentialsId)
304-
// Invalidate cached credentials to force fresh fetch
305-
getLogger().info(`Invalidated cached credentials for ${connectionId}`)
306-
globals.loginManager.store.invalidateCredentials(credentialsId)
307-
logger.info(`Looking for connection with ID: ${connectionId}`)
308-
309-
const connection = await Auth.instance.getConnection({ id: connectionId })
310-
if (connection === undefined) {
311-
// Log available connections for debugging
312-
const availableConnections = await Auth.instance.listConnections()
313-
logger.error(
314-
'Connection not found. Available connections: %O',
315-
availableConnections.map((c) => c.id)
316-
)
317-
throw new ToolkitError(`Failed to get connection from profile: ${connectionId}`, {
318-
code: 'MissingConnection',
319-
})
320-
}
291+
logger.info(`Activating profile: ${profileName}`)
292+
const credentialsId: CredentialsId = {
293+
credentialSource: 'profile',
294+
credentialTypeId: profileName,
295+
}
296+
const connectionId = asString(credentialsId)
297+
// Invalidate cached credentials to force fresh fetch
298+
getLogger().info(`Invalidated cached credentials for ${connectionId}`)
299+
globals.loginManager.store.invalidateCredentials(credentialsId)
300+
logger.info(`Looking for connection with ID: ${connectionId}`)
321301

322-
// Don't call useConnection() - let credentials be fetched naturally when needed
323-
await Auth.instance.updateConnectionState(connectionId, 'valid')
324-
} catch (error: any) {
325-
logger.error('Failed to activate profile: %O', error)
326-
void vscode.window.showErrorMessage(
327-
localize(
328-
'AWS.message.error.consoleLogin.profileActivationFailed',
329-
'Failed to activate profile: {0}',
330-
error instanceof Error ? error.message : String(error)
331-
)
302+
// Make sure that connection exists before letting other part use connection
303+
const connection = await Auth.instance.getConnection({ id: connectionId })
304+
if (connection === undefined) {
305+
// Log available connections for debugging
306+
const availableConnections = await Auth.instance.listConnections()
307+
logger.error(
308+
'Connection not found. Available connections: %O',
309+
availableConnections.map((c) => c.id)
332310
)
333-
throw new ToolkitError('Failed to activate profile', {
334-
code: 'ProfileActivationFailed',
335-
cause: error as Error,
311+
throw new ToolkitError(`Failed to get connection from profile: ${connectionId}`, {
312+
code: 'MissingConnection',
336313
})
337314
}
315+
// Don't call useConnection() - let credentials be fetched naturally when needed
316+
await Auth.instance.updateConnectionState(connectionId, 'valid')
338317
})
339318
}

packages/core/src/auth/providers/sharedCredentialsProvider.ts

Lines changed: 98 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ParsedIniData } from '@smithy/types'
1111
import { chain } from '@aws-sdk/property-provider'
1212
import { fromInstanceMetadata, fromContainerMetadata } from '@smithy/credential-provider-imds'
1313
import { fromEnv } from '@aws-sdk/credential-provider-env'
14+
import * as localizedText from '../../shared/localizedText'
1415
import { getLogger } from '../../shared/logger/logger'
1516
import { getStringHash } from '../../shared/utilities/textUtilities'
1617
import { getMfaTokenFromUser, resolveProviderWithCancel } from '../credentials/utils'
@@ -59,6 +60,96 @@ function isSsoProfile(profile: Profile): boolean {
5960
)
6061
}
6162

63+
export async function handleInvalidConsoleCredentials(
64+
error: Error,
65+
profileName: string,
66+
region: string
67+
): Promise<never> {
68+
getLogger().error('Console login authentication failed for profile %s in region %s: %O', profileName, region, error)
69+
70+
// Indicates that a VS Code window reload is required to reinitialize credential providers
71+
// and avoid using stale console session credentials when login cache and in-memory state diverge.
72+
let requiresVscodeReloadForCredentials = false
73+
if (
74+
error.message.includes('Your session has expired') ||
75+
error.message.includes('Failed to load a token for session') ||
76+
error.message.includes('Failed to load token from')
77+
) {
78+
requiresVscodeReloadForCredentials = true
79+
// Ask for user confirmation before refreshing
80+
const response = await vscode.window.showInformationMessage(
81+
`Unable to use your console credentials for profile "${profileName}". Would you like to retry?`,
82+
localizedText.retry,
83+
localizedText.cancel
84+
)
85+
86+
if (response !== localizedText.retry) {
87+
throw ToolkitError.chain(error, 'User cancelled console credentials token refresh.', {
88+
code: 'LoginSessionRefreshCancelled',
89+
cancelled: true,
90+
})
91+
}
92+
93+
getLogger().info('Re-authenticating using console credentials for profile %s', profileName)
94+
// Execute the console login command with the existing profile and region
95+
try {
96+
await vscode.commands.executeCommand('aws.toolkit.auth.consoleLogin', profileName, region)
97+
} catch (_) {
98+
void vscode.window.showErrorMessage(
99+
`Unable to refresh your AWS credentials. Please run 'aws login --profile ${profileName}' in your terminal, then reload VS Code to continue.`
100+
)
101+
}
102+
}
103+
104+
if (error.message.includes('does not contain login_session')) {
105+
// The credential provider was created before the CLI wrote the new login session to disk.
106+
// This happens when you run console login and immediately try to use the connection.
107+
// A window reload is needed to pick up the newly created session.
108+
requiresVscodeReloadForCredentials = true
109+
}
110+
111+
if (requiresVscodeReloadForCredentials) {
112+
getLogger().info(
113+
`Reloading window to sync with updated credentials cache using connection for profile: ${profileName}`
114+
)
115+
const reloadResponse = await vscode.window.showInformationMessage(
116+
`Credentials for "${profileName}" were updated. A window reload is required to apply them. Save your work before continuing. Reload now?`,
117+
localizedText.yes,
118+
localizedText.no
119+
)
120+
if (reloadResponse === localizedText.yes) {
121+
// At this point, the console credential cache on disk has been updated (via AWS CLI login),
122+
// but the in-memory credential providers used by the Toolkit / AWS SDK were already
123+
// constructed earlier and continue to reference stale credentials.
124+
//
125+
// Notes on behavior:
126+
// - Console credentials are read once when the provider is created and are not reloaded
127+
// dynamically at runtime.
128+
// - Removing or recreating connections/profiles does not rebuild the underlying provider.
129+
// - Filesystem watchers may detect cache changes, but provider instances still hold
130+
// the originally loaded credentials.
131+
// - Attempting to swap providers at runtime can introduce incompatibilities between
132+
// legacy credential shims and AWS SDK v3 providers.
133+
//
134+
// Authentication flow (simplified):
135+
// aws login (CLI) -> writes ~/.aws/login/cache
136+
// Toolkit -> constructs credential provider (snapshots credentials in memory)
137+
// SDK calls -> continue using in-memory credentials until provider is reinitialized
138+
//
139+
// A VS Code window reload is the only safe and deterministic way to fully reinitialize
140+
// credential providers and ensure the updated console session credentials are used.
141+
await vscode.commands.executeCommand('workbench.action.reloadWindow')
142+
}
143+
throw ToolkitError.chain(error, 'Console credentials require window reload', {
144+
code: 'FromLoginCredentialProviderError',
145+
})
146+
}
147+
148+
throw ToolkitError.chain(error, 'Console credentials error', {
149+
code: 'FromLoginCredentialProviderError',
150+
})
151+
}
152+
62153
/**
63154
* Represents one profile from the AWS Shared Credentials files.
64155
*/
@@ -400,67 +491,21 @@ export class SharedCredentialsProvider implements CredentialsProvider {
400491
const baseProvider = fromLoginCredentials({
401492
profile: this.profileName,
402493
clientConfig: {
494+
// Console session profiles created by 'aws login' may not have a region property
495+
// The AWS CLI's philosophy is to treat global options like --region as per-invocation overrides
496+
// rather than persistent configuration, minimizing what gets permanently stored in profiles
497+
// and deferring configuration decisions until the actual command execution.
403498
region: defaultRegion,
404499
},
405500
})
406501
return async () => {
407502
try {
408503
return await baseProvider()
409504
} catch (error) {
410-
getLogger().error(
411-
'Console login authentication failed for profile %s in region %s: %O',
412-
this.profileName,
413-
defaultRegion,
414-
error
415-
)
416-
417-
if (
418-
error instanceof Error &&
419-
(error.message.includes('Your session has expired') ||
420-
error.message.includes('Failed to load a token for session') ||
421-
error.message.includes('Failed to load token from'))
422-
) {
423-
// Ask for user confirmation before refreshing
424-
const response = await vscode.window.showInformationMessage(
425-
`Unable to use your console credentials for profile "${this.profileName}". Would you like to refresh it?`,
426-
'Refresh',
427-
'Cancel'
428-
)
429-
430-
if (response !== 'Refresh') {
431-
throw ToolkitError.chain(error, 'User cancelled console credentials token refresh.', {
432-
code: 'LoginSessionRefreshCancelled',
433-
cancelled: true,
434-
})
435-
}
436-
437-
getLogger().info('Re-authenticating using console credentials for profile %s', this.profileName)
438-
// Execute the console login command with the existing profile and region
439-
try {
440-
await vscode.commands.executeCommand(
441-
'aws.toolkit.auth.consoleLogin',
442-
this.profileName,
443-
defaultRegion
444-
)
445-
} catch (reAuthError) {
446-
throw ToolkitError.chain(
447-
reAuthError,
448-
`Failed to refresh credentials for profile ${this.profileName}. Run 'aws login --profile ${this.profileName}' to authenticate.`,
449-
{ code: 'LoginSessionReAuthError' }
450-
)
451-
}
452-
453-
getLogger().info(
454-
'Authentication completed for profile %s, refreshing credentials...',
455-
this.profileName
456-
)
457-
458-
// Use the same provider instance but get fresh credentials
459-
return await baseProvider()
505+
if (error instanceof Error) {
506+
await handleInvalidConsoleCredentials(error, this.profileName, defaultRegion)
460507
}
461-
throw ToolkitError.chain(error, `Failed to get console credentials`, {
462-
code: 'FromLoginCredentialProviderError',
463-
})
508+
throw error
464509
}
465510
}
466511
}

packages/core/src/awsService/sagemaker/commands.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export async function deeplinkConnect(
100100
appType?: string,
101101
workspaceName?: string,
102102
namespace?: string,
103-
clusterArn?: string,
103+
eksClusterArn?: string,
104104
isSMUS: boolean = false
105105
) {
106106
getLogger().debug(
@@ -118,11 +118,7 @@ export async function deeplinkConnect(
118118
appType: ${appType},
119119
workspaceName: ${workspaceName},
120120
namespace: ${namespace},
121-
clusterArn: ${clusterArn}`
122-
)
123-
124-
getLogger().info(
125-
`sm:deeplinkConnect: domain: ${domain}, appType: ${appType}, workspaceName: ${workspaceName}, namespace: ${namespace}, clusterArn: ${clusterArn}`
121+
eksClusterArn: ${eksClusterArn}`
126122
)
127123

128124
if (isRemoteWorkspace()) {
@@ -132,8 +128,8 @@ export async function deeplinkConnect(
132128

133129
try {
134130
let connectionType = 'sm_dl'
135-
if (!domain && clusterArn && workspaceName && namespace) {
136-
const { accountId, region, clusterName } = parseArn(clusterArn)
131+
if (!domain && eksClusterArn && workspaceName && namespace) {
132+
const { accountId, region, clusterName } = parseArn(eksClusterArn)
137133
connectionType = 'sm_hp'
138134
const proposedSession = `${workspaceName}_${namespace}_${clusterName}_${region}_${accountId}`
139135
session = isValidSshHostname(proposedSession)
@@ -224,21 +220,22 @@ function createValidSshSession(
224220
const components = [
225221
sanitize(workspaceName, 63), // K8s limit
226222
sanitize(namespace, 63), // K8s limit
227-
sanitize(clusterName, 63), // HP cluster limit
223+
sanitize(clusterName, 100), // EKS limit
228224
sanitize(region, 16), // Longest AWS region limit
229225
sanitize(accountId, 12), // Fixed
230226
].filter((c) => c.length > 0)
231-
// Total: 63 + 63 + 63 + 16 + 12 + 4 separators + 3 chars for hostname header = 224 < 253 (max limit)
227+
// Total: 63 + 63 + 100 + 16 + 12 + 4 separators + 3 chars for hostname header = 261 > 253 (max limit)
228+
// If all attributes max out char limit, then accountId will be truncated to the first 4 char.
232229

233-
const session = components.join('_').substring(0, 224)
230+
const session = components.join('_').substring(0, 253)
234231
return session
235232
}
236233

237234
/**
238235
* Validates if a string meets SSH hostname naming convention
239236
*/
240237
function isValidSshHostname(label: string): boolean {
241-
return /^[a-z0-9]([a-z0-9.-_]{0,222}[a-z0-9])?$/.test(label)
238+
return /^[a-z0-9]([a-z0-9.-_]{0,251}[a-z0-9])?$/.test(label)
242239
}
243240

244241
export async function stopSpace(

packages/core/src/awsService/sagemaker/uriHandlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function register(ctx: ExtContext) {
3838
undefined,
3939
params.workspaceName,
4040
params.namespace,
41-
params.clusterArn
41+
params.eksClusterArn
4242
)
4343
})
4444
}
@@ -51,7 +51,7 @@ export function register(ctx: ExtContext) {
5151

5252
export function parseHyperpodConnectParams(query: SearchParams) {
5353
const requiredParams = query.getFromKeysOrThrow('sessionId', 'streamUrl', 'sessionToken', 'cell-number')
54-
const optionalParams = query.getFromKeys('workspaceName', 'namespace', 'clusterArn')
54+
const optionalParams = query.getFromKeys('workspaceName', 'namespace', 'eksClusterArn')
5555
return { ...requiredParams, ...optionalParams }
5656
}
5757
export function parseConnectParams(query: SearchParams) {

packages/core/src/codewhisperer/util/codewhispererSettings.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const description = {
1616
workspaceIndexMaxFileSize: Number,
1717
workspaceIndexCacheDirPath: String,
1818
workspaceIndexIgnoreFilePatterns: ArrayConstructor(String),
19-
allowFeatureDevelopmentToRunCodeAndTests: Object,
2019
ignoredSecurityIssues: ArrayConstructor(String),
2120
}
2221

@@ -74,18 +73,6 @@ export class CodeWhispererSettings extends fromExtensionManifest('amazonQ', desc
7473
return this.get('workspaceIndexIgnoreFilePatterns', [])
7574
}
7675

77-
public getAutoBuildSetting(): { [key: string]: boolean } {
78-
return this.get('allowFeatureDevelopmentToRunCodeAndTests', {})
79-
}
80-
81-
public async updateAutoBuildSetting(projectName: string, setting: boolean) {
82-
const projects = this.getAutoBuildSetting()
83-
84-
projects[projectName] = setting
85-
86-
await this.update('allowFeatureDevelopmentToRunCodeAndTests', projects)
87-
}
88-
8976
public getIgnoredSecurityIssues(): string[] {
9077
return this.get('ignoredSecurityIssues', [])
9178
}

0 commit comments

Comments
 (0)