Skip to content

Commit f297d64

Browse files
telemetry: add project account id and region (aws#8079)
## Problem The project account id and region was missing from data connection and space action telemetry. ## Solution The project account id and region are added to data connection and space action telemetry. Telemetry for a cross-account example: ``` 2025-09-23 15:42:29.674 [debug] telemetry: smus_login { Metadata: { metricId: 'fdbf85ac-a093-4ab9-ac47-add57a5901cc', traceId: '7ee3db34-3f9b-41b7-aa16-824290fa0719', parentId: '5c66c1e2-09ba-4308-8e28-a47bfc4acdda', smusDomainId: 'dzd_64o7tjjv1cm9gp', awsRegion: 'us-east-2', smusDomainAccountId: '730335272067', duration: '24121', result: 'Succeeded', awsAccount: 'not-set' }, Value: 1, Unit: 'None', Passive: false } 2025-09-23 15:42:32.466 [debug] telemetry: smus_accessProject { Metadata: { metricId: 'cc75867d-cb7c-4392-b7d7-cda1f7ba627c', traceId: '7ee3db34-3f9b-41b7-aa16-824290fa0719', parentId: 'da7fad30-0aee-4f31-8962-00ef50b408b8', smusDomainId: 'dzd_64o7tjjv1cm9gp', smusProjectId: 'cxtwtxb6e3ly95', smusDomainRegion: 'us-east-2', smusDomainAccountId: '730335272067', duration: '3994', result: 'Succeeded', awsAccount: 'not-set', awsRegion: 'us-east-1' }, Value: 1, Unit: 'None', Passive: false } 2025-09-23 15:42:43.475 [debug] telemetry: smus_renderLakehouseNode { Metadata: { metricId: 'dc09a62e-9f18-45c5-bd8b-c113c6a8c5c9', traceId: '58ea5647-a29a-45dc-9e6c-fb4175a34a6d', smusToolkitEnv: 'local', smusDomainId: 'dzd_64o7tjjv1cm9gp', smusDomainAccountId: '730335272067', smusProjectId: 'cxtwtxb6e3ly95', smusConnectionId: '4r6iscfi0rih0p', smusConnectionType: 'LAKEHOUSE', smusProjectRegion: 'us-east-1', smusProjectAccountId: '976193268201', duration: '965', result: 'Succeeded', awsAccount: 'not-set', awsRegion: 'us-east-1' }, Value: 1, Unit: 'None', Passive: false } 2025-09-23 15:42:46.623 [debug] telemetry: smus_renderS3Node { Metadata: { metricId: 'be029b31-6111-48f1-8e66-99121dd48484', traceId: 'a3698692-e948-4ec4-881a-17a0443e109d', smusToolkitEnv: 'local', smusDomainId: 'dzd_64o7tjjv1cm9gp', smusDomainAccountId: '730335272067', smusProjectId: 'cxtwtxb6e3ly95', smusConnectionId: '6gy7b7go2jd50p', smusConnectionType: 'S3', smusProjectRegion: 'us-east-1', smusProjectAccountId: '976193268201', duration: '1', result: 'Succeeded', awsAccount: 'not-set', awsRegion: 'us-east-1' }, Value: 1, Unit: 'None', Passive: false } 2025-09-23 15:43:04.774 [debug] telemetry: smus_openRemoteConnection { Metadata: { metricId: '7f2c4573-b681-4e81-bc34-84509aac1f46', traceId: 'fc5d6674-7042-4634-b7aa-1098aee2b540', smusSpaceKey: 'd-uyehbqjlnjl0__ce', smusDomainRegion: 'us-east-1', smusDomainId: 'dzd_64o7tjjv1cm9gp', smusDomainAccountId: '730335272067', smusProjectId: 'cxtwtxb6e3ly95', smusProjectAccountId: '976193268201', smusProjectRegion: 'us-east-1', duration: '6969', result: 'Succeeded', awsAccount: 'not-set', awsRegion: 'us-east-1' }, Value: 1, Unit: 'None', Passive: false } ``` --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Co-authored-by: Laxman Reddy <[email protected]>
1 parent aa4335c commit f297d64

File tree

7 files changed

+477
-96
lines changed

7 files changed

+477
-96
lines changed

packages/core/src/sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider.ts

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as localizedText from '../../../shared/localizedText'
1414
import { ToolkitPromptSettings } from '../../../shared/settings'
1515
import { setContext, getContext } from '../../../shared/vscode/setContext'
1616
import { getLogger } from '../../../shared/logger/logger'
17-
import { SmusUtils, SmusErrorCodes, extractAccountIdFromArn } from '../../shared/smusUtils'
17+
import { SmusUtils, SmusErrorCodes, extractAccountIdFromResourceMetadata } from '../../shared/smusUtils'
1818
import { createSmusProfile, isValidSmusConnection, SmusConnection } from '../model'
1919
import { DomainExecRoleCredentialsProvider } from './domainExecRoleCredentialsProvider'
2020
import { ProjectRoleCredentialsProvider } from './projectRoleCredentialsProvider'
@@ -24,6 +24,7 @@ import { getResourceMetadata } from '../../shared/utils/resourceMetadataUtils'
2424
import { fromIni } from '@aws-sdk/credential-providers'
2525
import { randomUUID } from '../../../shared/crypto'
2626
import { DefaultStsClient } from '../../../shared/clients/stsClient'
27+
import { DataZoneClient } from '../../shared/client/datazoneClient'
2728

2829
/**
2930
* Sets the context variable for SageMaker Unified Studio connection state
@@ -55,6 +56,7 @@ export class SmusAuthenticationProvider {
5556
private projectCredentialProvidersCache = new Map<string, ProjectRoleCredentialsProvider>()
5657
private connectionCredentialProvidersCache = new Map<string, ConnectionCredentialsProvider>()
5758
private cachedDomainAccountId: string | undefined
59+
private cachedProjectAccountIds = new Map<string, string>()
5860

5961
public constructor(
6062
public readonly auth = Auth.instance,
@@ -79,6 +81,8 @@ export class SmusAuthenticationProvider {
7981
this.connectionCredentialProvidersCache.clear()
8082
// Clear cached domain account ID when connection changes
8183
this.cachedDomainAccountId = undefined
84+
// Clear cached project account IDs when connection changes
85+
this.cachedProjectAccountIds.clear()
8286
// Clear all clients in client store when connection changes
8387
ConnectionClientStore.getInstance().clearAll()
8488
await setSmusConnectedContext(this.isConnected())
@@ -445,37 +449,13 @@ export class SmusAuthenticationProvider {
445449

446450
// If in SMUS space environment, extract account ID from resource-metadata file
447451
if (getContext('aws.smus.inSmusSpaceEnvironment')) {
448-
try {
449-
logger.debug('SMUS: Extracting domain account ID from ResourceArn in resource-metadata file')
450-
451-
const resourceMetadata = getResourceMetadata()!
452-
const resourceArn = resourceMetadata.ResourceArn
453-
454-
if (!resourceArn) {
455-
throw new ToolkitError('ResourceArn not found in metadata file', {
456-
code: SmusErrorCodes.AccountIdNotFound,
457-
})
458-
}
452+
const accountId = await extractAccountIdFromResourceMetadata()
459453

460-
// Extract account ID from ResourceArn using SmusUtils
461-
const accountId = extractAccountIdFromArn(resourceArn)
462-
463-
// Cache the account ID
464-
this.cachedDomainAccountId = accountId
465-
466-
logger.debug(
467-
`Successfully extracted and cached domain account ID from resource-metadata file: ${accountId}`
468-
)
469-
470-
return accountId
471-
} catch (err) {
472-
logger.error(`Failed to extract domain account ID from ResourceArn: %s`, err)
454+
// Cache the account ID
455+
this.cachedDomainAccountId = accountId
456+
logger.debug(`Successfully cached domain account ID: ${accountId}`)
473457

474-
throw new ToolkitError('Failed to extract AWS account ID from ResourceArn in SMUS space environment', {
475-
code: SmusErrorCodes.GetDomainAccountIdFailed,
476-
cause: err instanceof Error ? err : undefined,
477-
})
478-
}
458+
return accountId
479459
}
480460

481461
if (!this.activeConnection) {
@@ -520,6 +500,81 @@ export class SmusAuthenticationProvider {
520500
}
521501
}
522502

503+
/**
504+
* Gets the AWS account ID for a specific project using project credentials
505+
* In SMUS space environment, extracts from ResourceArn in metadata (same as domain account)
506+
* Otherwise, makes an STS GetCallerIdentity call using project credentials
507+
* @param projectId The DataZone project ID
508+
* @returns Promise resolving to the project's AWS account ID
509+
*/
510+
public async getProjectAccountId(projectId: string): Promise<string> {
511+
const logger = getLogger()
512+
513+
// Return cached value if available
514+
if (this.cachedProjectAccountIds.has(projectId)) {
515+
logger.debug(`SMUS: Using cached project account ID for project ${projectId}`)
516+
return this.cachedProjectAccountIds.get(projectId)!
517+
}
518+
519+
// If in SMUS space environment, extract account ID from resource-metadata file
520+
if (getContext('aws.smus.inSmusSpaceEnvironment')) {
521+
const accountId = await extractAccountIdFromResourceMetadata()
522+
523+
// Cache the account ID
524+
this.cachedProjectAccountIds.set(projectId, accountId)
525+
logger.debug(`Successfully cached project account ID for project ${projectId}: ${accountId}`)
526+
527+
return accountId
528+
}
529+
530+
if (!this.activeConnection) {
531+
throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
532+
}
533+
534+
// For non-SMUS space environments, use project credentials with STS
535+
try {
536+
logger.debug('Fetching project account ID via STS GetCallerIdentity with project credentials')
537+
538+
// Get project credentials
539+
const projectCredProvider = await this.getProjectCredentialProvider(projectId)
540+
const projectCreds = await projectCredProvider.getCredentials()
541+
542+
// Get project region from tooling environment
543+
const dzClient = await DataZoneClient.getInstance(this)
544+
const toolingEnv = await dzClient.getToolingEnvironment(projectId)
545+
const projectRegion = toolingEnv.awsAccountRegion
546+
547+
if (!projectRegion) {
548+
throw new ToolkitError('No AWS account region found in tooling environment', {
549+
code: SmusErrorCodes.RegionNotFound,
550+
})
551+
}
552+
553+
// Use STS to get account ID from project credentials
554+
const stsClient = new DefaultStsClient(projectRegion, projectCreds)
555+
const callerIdentity = await stsClient.getCallerIdentity()
556+
557+
if (!callerIdentity.Account) {
558+
throw new ToolkitError('Account ID not found in STS GetCallerIdentity response', {
559+
code: SmusErrorCodes.AccountIdNotFound,
560+
})
561+
}
562+
563+
// Cache the account ID
564+
this.cachedProjectAccountIds.set(projectId, callerIdentity.Account)
565+
logger.debug(
566+
`Successfully retrieved and cached project account ID for project ${projectId}: ${callerIdentity.Account}`
567+
)
568+
569+
return callerIdentity.Account
570+
} catch (err) {
571+
logger.error('Failed to get project account ID: %s', err as Error)
572+
throw new ToolkitError(`Failed to get project account ID: ${(err as Error).message}`, {
573+
code: SmusErrorCodes.GetProjectAccountIdFailed,
574+
})
575+
}
576+
}
577+
523578
public getDomainRegion(): string {
524579
if (getContext('aws.smus.inSmusSpaceEnvironment')) {
525580
const resourceMetadata = getResourceMetadata()!
@@ -617,6 +672,10 @@ export class SmusAuthenticationProvider {
617672
// Clear cached domain account ID
618673
this.cachedDomainAccountId = undefined
619674
logger.debug('SMUS: Cleared cached domain account ID')
675+
676+
// Clear cached project account IDs
677+
this.cachedProjectAccountIds.clear()
678+
logger.debug('SMUS: Cleared cached project account IDs')
620679
}
621680

622681
/**
@@ -665,6 +724,9 @@ export class SmusAuthenticationProvider {
665724
// Clear cached domain account ID
666725
this.cachedDomainAccountId = undefined
667726

727+
// Clear cached project account IDs
728+
this.cachedProjectAccountIds.clear()
729+
668730
this.logger.debug('SMUS Auth: Successfully disposed authentication provider')
669731
}
670732

packages/core/src/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { createPlaceholderItem } from '../../../shared/treeview/utils'
2424
import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
2525
import { GlueCatalog } from '../../shared/client/glueCatalogClient'
2626
import { telemetry } from '../../../shared/telemetry/telemetry'
27-
import { getContext } from '../../../shared/vscode/setContext'
27+
import { recordDataConnectionTelemetry } from '../../shared/telemetry'
2828

2929
/**
3030
* Redshift data node for SageMaker Unified Studio
@@ -119,6 +119,7 @@ export function createRedshiftConnectionNode(
119119
connection: DataZoneConnection,
120120
connectionCredentialsProvider: ConnectionCredentialsProvider
121121
): RedshiftNode {
122+
const logger = getLogger()
122123
return new RedshiftNode(
123124
{
124125
id: connection.connectionId,
@@ -130,19 +131,8 @@ export function createRedshiftConnectionNode(
130131
},
131132
async (node) => {
132133
return telemetry.smus_renderRedshiftNode.run(async (span) => {
133-
const isInSmusSpace = getContext('aws.smus.inSmusSpaceEnvironment')
134-
const accountId = await connectionCredentialsProvider.getDomainAccountId()
135-
span.record({
136-
smusToolkitEnv: isInSmusSpace ? 'smus_space' : 'local',
137-
smusDomainId: connection.domainId,
138-
smusDomainAccountId: accountId,
139-
smusProjectId: connection.projectId,
140-
smusConnectionId: connection.connectionId,
141-
smusConnectionType: connection.type,
142-
smusProjectRegion: connection.location?.awsRegion,
143-
})
144-
const logger = getLogger()
145134
logger.info(`Loading Redshift resources for connection ${connection.name}`)
135+
await recordDataConnectionTelemetry(span, connection, connectionCredentialsProvider)
146136

147137
const connectionParams = extractConnectionParams(connection)
148138
if (!connectionParams) {

packages/core/src/sagemakerunifiedstudio/shared/smusUtils.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,14 @@ export const SmusErrorCodes = {
5656
UserCancelled: 'UserCancelled',
5757
/** Error code for when domain account Id is missing */
5858
AccountIdNotFound: 'AccountIdNotFound',
59+
/** Error code for when resource ARN is missing */
60+
ResourceArnNotFound: 'ResourceArnNotFound',
5961
/** Error code for when fails to get domain account Id */
6062
GetDomainAccountIdFailed: 'GetDomainAccountIdFailed',
63+
/** Error code for when fails to get project account Id */
64+
GetProjectAccountIdFailed: 'GetProjectAccountIdFailed',
65+
/** Error code for when region is missing */
66+
RegionNotFound: 'RegionNotFound',
6167
} as const
6268

6369
/**
@@ -369,9 +375,9 @@ export class SmusUtils {
369375
* @returns The account ID from the ARN
370376
* @throws If the ARN format is invalid
371377
*/
372-
export function extractAccountIdFromArn(arn: string): string {
378+
export function extractAccountIdFromSageMakerArn(arn: string): string {
373379
// Match the ARN components to extract account ID
374-
const regex = /^arn:aws:sagemaker:(?<region>[^:]+):(?<accountId>\d+):(app|space|domain)\/.+$/i
380+
const regex = /^arn:aws:sagemaker:(?<region>[^:]+):(?<accountId>\d+):(app|space)\/.+$/i
375381
const match = arn.match(regex)
376382

377383
if (!match?.groups) {
@@ -380,3 +386,31 @@ export function extractAccountIdFromArn(arn: string): string {
380386

381387
return match.groups.accountId
382388
}
389+
390+
/**
391+
* Extracts account ID from ResourceArn in SMUS space environment
392+
* @returns Promise resolving to the account ID
393+
* @throws ToolkitError if unable to extract account ID
394+
*/
395+
export async function extractAccountIdFromResourceMetadata(): Promise<string> {
396+
const logger = getLogger()
397+
398+
try {
399+
logger.debug('SMUS: Extracting account ID from ResourceArn in resource-metadata file')
400+
401+
const resourceMetadata = getResourceMetadata()!
402+
const resourceArn = resourceMetadata.ResourceArn
403+
404+
if (!resourceArn) {
405+
throw new Error('ResourceArn not found in metadata file')
406+
}
407+
408+
const accountId = extractAccountIdFromSageMakerArn(resourceArn)
409+
logger.debug(`Successfully extracted account ID from resource-metadata file: ${accountId}`)
410+
411+
return accountId
412+
} catch (err) {
413+
logger.error(`Failed to extract account ID from ResourceArn: %s`, err)
414+
throw new Error('Failed to extract AWS account ID from ResourceArn in SMUS space environment')
415+
}
416+
}

packages/core/src/sagemakerunifiedstudio/shared/telemetry.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { SmusAuthenticationProvider } from '../auth/providers/smusAuthentication
1818
import { getLogger } from '../../shared/logger/logger'
1919
import { getContext } from '../../shared/vscode/setContext'
2020
import { ConnectionCredentialsProvider } from '../auth/providers/connectionCredentialsProvider'
21-
import { DataZoneConnection } from './client/datazoneClient'
21+
import { DataZoneConnection, DataZoneClient } from './client/datazoneClient'
2222

2323
/**
2424
* Records space telemetry
@@ -27,16 +27,39 @@ export async function recordSpaceTelemetry(
2727
span: Span<SmusOpenRemoteConnection> | Span<SmusStopSpace>,
2828
node: SagemakerUnifiedStudioSpaceNode
2929
) {
30-
const parent = node.resource.getParent() as SageMakerUnifiedStudioSpacesParentNode
31-
const authProvider = SmusAuthenticationProvider.fromContext()
32-
const accountId = await authProvider.getDomainAccountId()
33-
span.record({
34-
smusSpaceKey: node.resource.DomainSpaceKey,
35-
smusDomainRegion: node.resource.regionCode,
36-
smusDomainId: parent?.getAuthProvider()?.activeConnection?.domainId,
37-
smusDomainAccountId: accountId,
38-
smusProjectId: parent?.getProjectId(),
39-
})
30+
const logger = getLogger()
31+
32+
try {
33+
const parent = node.resource.getParent() as SageMakerUnifiedStudioSpacesParentNode
34+
const authProvider = SmusAuthenticationProvider.fromContext()
35+
const accountId = await authProvider.getDomainAccountId()
36+
const projectId = parent?.getProjectId()
37+
38+
// Get project account ID and region
39+
let projectAccountId: string | undefined
40+
let projectRegion: string | undefined
41+
42+
if (projectId) {
43+
projectAccountId = await authProvider.getProjectAccountId(projectId)
44+
45+
// Get project region from tooling environment
46+
const dzClient = await DataZoneClient.getInstance(authProvider)
47+
const toolingEnv = await dzClient.getToolingEnvironment(projectId)
48+
projectRegion = toolingEnv.awsAccountRegion
49+
}
50+
51+
span.record({
52+
smusSpaceKey: node.resource.DomainSpaceKey,
53+
smusDomainRegion: node.resource.regionCode,
54+
smusDomainId: parent?.getAuthProvider()?.activeConnection?.domainId,
55+
smusDomainAccountId: accountId,
56+
smusProjectId: projectId,
57+
smusProjectAccountId: projectAccountId,
58+
smusProjectRegion: projectRegion,
59+
})
60+
} catch (err) {
61+
logger.error(`Failed to record space telemetry: ${(err as Error).message}`)
62+
}
4063
}
4164

4265
/**
@@ -65,7 +88,7 @@ export async function recordAuthTelemetry(
6588
})
6689
} catch (err) {
6790
logger.error(
68-
`Failed to resolve AWS account ID via STS Client for domain ${domainId} in region ${region}: ${err}`
91+
`Failed to record Domain AccountId in data connection telemetry for domain ${domainId} in region ${region}: ${err}`
6992
)
7093
}
7194
}
@@ -78,15 +101,22 @@ export async function recordDataConnectionTelemetry(
78101
connection: DataZoneConnection,
79102
connectionCredentialsProvider: ConnectionCredentialsProvider
80103
) {
81-
const isInSmusSpace = getContext('aws.smus.inSmusSpaceEnvironment')
82-
const accountId = await connectionCredentialsProvider.getDomainAccountId()
83-
span.record({
84-
smusToolkitEnv: isInSmusSpace ? 'smus_space' : 'local',
85-
smusDomainId: connection.domainId,
86-
smusDomainAccountId: accountId,
87-
smusProjectId: connection.projectId,
88-
smusConnectionId: connection.connectionId,
89-
smusConnectionType: connection.type,
90-
smusProjectRegion: connection.location?.awsRegion,
91-
})
104+
const logger = getLogger()
105+
106+
try {
107+
const isInSmusSpace = getContext('aws.smus.inSmusSpaceEnvironment')
108+
const accountId = await connectionCredentialsProvider.getDomainAccountId()
109+
span.record({
110+
smusToolkitEnv: isInSmusSpace ? 'smus_space' : 'local',
111+
smusDomainId: connection.domainId,
112+
smusDomainAccountId: accountId,
113+
smusProjectId: connection.projectId,
114+
smusConnectionId: connection.connectionId,
115+
smusConnectionType: connection.type,
116+
smusProjectRegion: connection.location?.awsRegion,
117+
smusProjectAccountId: connection.location?.awsAccountId,
118+
})
119+
} catch (err) {
120+
logger.error(`Failed to record data connection telemetry: ${(err as Error).message}`)
121+
}
92122
}

0 commit comments

Comments
 (0)