Skip to content

Commit 9b0a538

Browse files
feat: Extract resource arn and remote resource access key for cross-account support (#192)
**Description of changes:** 1. Add auto-instrumentation support for the following AWS resources. - Populate `aws.stream.arn` in Span by extracting StreamArn from the request body. - Populate `aws.table.arn` in Span by extracting TableArn from the response body. 2. Add auto-instrumentation support for remote resource access key. - Populate `aws.auth.account.access_key` and `aws.auth.region` in Span from STS credentials in client config 3. Generate cross-account metrics attributes when remote resource identifier is present - If remote resource arn is available, extract account id and region from arn. - Otherwise, pass account access key id and region from span to metric if available. 4. Add unit tests, contract tests. Done E2E tests with CW agent. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 6d182ad commit 9b0a538

File tree

13 files changed

+1012
-109
lines changed

13 files changed

+1012
-109
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import { SEMATTRS_AWS_DYNAMODB_TABLE_NAMES } from '@opentelemetry/semantic-conve
55

66
// Utility class holding attribute keys with special meaning to AWS components
77
export const AWS_ATTRIBUTE_KEYS = {
8+
AWS_AUTH_ACCOUNT_ACCESS_KEY: 'aws.auth.account.access_key',
9+
AWS_AUTH_REGION: 'aws.auth.region',
810
AWS_SPAN_KIND: 'aws.span.kind',
911
AWS_LOCAL_SERVICE: 'aws.local.service',
1012
AWS_LOCAL_OPERATION: 'aws.local.operation',
1113
AWS_REMOTE_SERVICE: 'aws.remote.service',
1214
AWS_REMOTE_ENVIRONMENT: 'aws.remote.environment',
1315
AWS_REMOTE_OPERATION: 'aws.remote.operation',
16+
AWS_REMOTE_RESOURCE_ACCOUNT_ACCESS_KEY: 'aws.remote.resource.account.access_key',
17+
AWS_REMOTE_RESOURCE_ACCOUNT_ID: 'aws.remote.resource.account.id',
18+
AWS_REMOTE_RESOURCE_REGION: 'aws.remote.resource.region',
1419
AWS_REMOTE_RESOURCE_TYPE: 'aws.remote.resource.type',
1520
AWS_REMOTE_RESOURCE_IDENTIFIER: 'aws.remote.resource.identifier',
1621
AWS_SDK_DESCENDANT: 'aws.sdk.descendant',
@@ -31,7 +36,9 @@ export const AWS_ATTRIBUTE_KEYS = {
3136
AWS_S3_BUCKET: 'aws.s3.bucket',
3237
AWS_SQS_QUEUE_URL: 'aws.sqs.queue.url',
3338
AWS_SQS_QUEUE_NAME: 'aws.sqs.queue.name',
39+
AWS_KINESIS_STREAM_ARN: 'aws.kinesis.stream.arn',
3440
AWS_KINESIS_STREAM_NAME: 'aws.kinesis.stream.name',
41+
AWS_DYNAMODB_TABLE_ARN: 'aws.dynamodb.table.arn',
3542
AWS_DYNAMODB_TABLE_NAMES: SEMATTRS_AWS_DYNAMODB_TABLE_NAMES,
3643
AWS_BEDROCK_DATA_SOURCE_ID: 'aws.bedrock.data_source.id',
3744
AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id',

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
MetricAttributeGenerator,
3333
SERVICE_METRIC,
3434
} from './metric-attribute-generator';
35+
import { RegionalResourceArnParser } from './regional-resource-arn-parser';
3536
import { SqsUrlParser } from './sqs-url-parser';
3637
import { LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT } from './aws-opentelemetry-configurator';
3738

@@ -112,8 +113,20 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
112113
AwsMetricAttributeGenerator.setService(resource, span, attributes);
113114
AwsMetricAttributeGenerator.setEgressOperation(span, attributes);
114115
AwsMetricAttributeGenerator.setRemoteServiceAndOperation(span, attributes);
115-
AwsMetricAttributeGenerator.setRemoteResourceTypeAndIdentifier(span, attributes);
116+
const isRemoteResourceIdentifierPresent = AwsMetricAttributeGenerator.setRemoteResourceTypeAndIdentifier(
117+
span,
118+
attributes
119+
);
116120
AwsMetricAttributeGenerator.setRemoteEnvironment(span, attributes);
121+
if (isRemoteResourceIdentifierPresent) {
122+
const isAccountIdAndRegionPresent = AwsMetricAttributeGenerator.setRemoteResourceAccountIdAndRegion(
123+
span,
124+
attributes
125+
);
126+
if (!isAccountIdAndRegionPresent) {
127+
AwsMetricAttributeGenerator.setRemoteResourceAccessKeyAndRegion(span, attributes);
128+
}
129+
}
117130
AwsMetricAttributeGenerator.setSpanKindForDependency(span, attributes);
118131
AwsMetricAttributeGenerator.setRemoteDbUser(span, attributes);
119132

@@ -369,7 +382,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
369382
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
370383
* Cloud Control resource format</a>.
371384
*/
372-
private static setRemoteResourceTypeAndIdentifier(span: ReadableSpan, attributes: Attributes): void {
385+
private static setRemoteResourceTypeAndIdentifier(span: ReadableSpan, attributes: Attributes): boolean {
373386
let remoteResourceType: AttributeValue | undefined;
374387
let remoteResourceIdentifier: AttributeValue | undefined;
375388
let cloudFormationIdentifier: AttributeValue | undefined;
@@ -383,11 +396,25 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
383396
) {
384397
remoteResourceType = NORMALIZED_DYNAMO_DB_SERVICE_NAME + '::Table';
385398
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(awsTableNames[0]);
399+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_ARN)) {
400+
remoteResourceType = NORMALIZED_DYNAMO_DB_SERVICE_NAME + '::Table';
401+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
402+
RegionalResourceArnParser.extractDynamoDbTableNameFromArn(
403+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_ARN]
404+
)
405+
);
386406
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME)) {
387407
remoteResourceType = NORMALIZED_KINESIS_SERVICE_NAME + '::Stream';
388408
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
389409
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME]
390410
);
411+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_ARN)) {
412+
remoteResourceType = NORMALIZED_KINESIS_SERVICE_NAME + '::Stream';
413+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
414+
RegionalResourceArnParser.extractKinesisStreamNameFromArn(
415+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_ARN]
416+
)
417+
);
391418
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET)) {
392419
remoteResourceType = NORMALIZED_S3_SERVICE_NAME + '::Bucket';
393420
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
@@ -398,31 +425,31 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
398425

399426
remoteResourceType = NORMALIZED_SNS_SERVICE_NAME + '::Topic';
400427
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
401-
this.extractResourceNameFromArn(snsArn)
428+
RegionalResourceArnParser.extractResourceNameFromArn(snsArn)
402429
);
403430
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(snsArn);
404431
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN)) {
405432
const secretsArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN];
406433

407434
remoteResourceType = NORMALIZED_SECRETSMANAGER_SERVICE_NAME + '::Secret';
408435
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
409-
this.extractResourceNameFromArn(secretsArn)
436+
RegionalResourceArnParser.extractResourceNameFromArn(secretsArn)
410437
);
411438
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(secretsArn);
412439
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN)) {
413440
const stateMachineArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN];
414441

415442
remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::StateMachine';
416443
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
417-
this.extractResourceNameFromArn(stateMachineArn)
444+
RegionalResourceArnParser.extractResourceNameFromArn(stateMachineArn)
418445
);
419446
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(stateMachineArn);
420447
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN)) {
421448
const activityArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN];
422449

423450
remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::Activity';
424451
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
425-
this.extractResourceNameFromArn(activityArn)
452+
RegionalResourceArnParser.extractResourceNameFromArn(activityArn)
426453
);
427454
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(activityArn);
428455
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME)) {
@@ -500,7 +527,10 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
500527
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType;
501528
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier;
502529
attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier;
530+
return true;
503531
}
532+
533+
return false;
504534
}
505535

506536
/**
@@ -522,6 +552,56 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
522552
}
523553
}
524554

555+
private static setRemoteResourceAccountIdAndRegion(span: ReadableSpan, attributes: Attributes): boolean {
556+
const ARN_ATTRIBUTES: string[] = [
557+
AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_ARN,
558+
AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_ARN,
559+
AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN,
560+
AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN,
561+
AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN,
562+
AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN,
563+
AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_ARN,
564+
AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN,
565+
];
566+
let remoteResourceAccountId: string | undefined = undefined;
567+
let remoteResourceRegion: string | undefined = undefined;
568+
569+
if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL)) {
570+
const sqsQueueUrl = AwsMetricAttributeGenerator.escapeDelimiters(
571+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]
572+
);
573+
remoteResourceAccountId = SqsUrlParser.getAccountId(sqsQueueUrl);
574+
remoteResourceRegion = SqsUrlParser.getRegion(sqsQueueUrl);
575+
} else {
576+
for (const attributeKey of ARN_ATTRIBUTES) {
577+
if (AwsSpanProcessingUtil.isKeyPresent(span, attributeKey)) {
578+
const arn = span.attributes[attributeKey];
579+
remoteResourceAccountId = RegionalResourceArnParser.getAccountId(arn);
580+
remoteResourceRegion = RegionalResourceArnParser.getRegion(arn);
581+
break;
582+
}
583+
}
584+
}
585+
586+
if (remoteResourceAccountId !== undefined && remoteResourceRegion !== undefined) {
587+
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_ACCOUNT_ID] = remoteResourceAccountId;
588+
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_REGION] = remoteResourceRegion;
589+
return true;
590+
}
591+
592+
return false;
593+
}
594+
595+
private static setRemoteResourceAccessKeyAndRegion(span: ReadableSpan, attributes: Attributes): void {
596+
if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY)) {
597+
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_ACCOUNT_ACCESS_KEY] =
598+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY];
599+
}
600+
if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION)) {
601+
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_REGION] = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION];
602+
}
603+
}
604+
525605
/**
526606
* RemoteResourceIdentifier is populated with rule <code>
527607
* ^[{db.name}|]?{address}[|{port}]?
@@ -649,16 +729,6 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
649729
return rpcService === 'Lambda' && span.attributes[SEMATTRS_RPC_METHOD] === LAMBDA_INVOKE_OPERATION;
650730
}
651731

652-
// Extracts the name of the resource from an arn
653-
private static extractResourceNameFromArn(attribute: AttributeValue | undefined): string | undefined {
654-
if (typeof attribute === 'string' && attribute.startsWith('arn:aws:')) {
655-
const split = attribute.split(':');
656-
return split[split.length - 1];
657-
}
658-
659-
return undefined;
660-
}
661-
662732
/** Span kind is needed for differentiating metrics in the EMF exporter */
663733
private static setSpanKindForService(span: ReadableSpan, attributes: Attributes): void {
664734
let spanKind: string = SpanKind[span.kind];

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[])
8080
patchSqsServiceExtension(services.get('SQS'));
8181
patchSnsServiceExtension(services.get('SNS'));
8282
patchLambdaServiceExtension(services.get('Lambda'));
83+
patchKinesisServiceExtension(services.get('Kinesis'));
84+
patchDynamoDbServiceExtension(services.get('DynamoDB'));
8385
}
8486
} else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') {
8587
diag.debug('Patching aws lambda instrumentation');
@@ -189,6 +191,69 @@ function patchSnsServiceExtension(snsServiceExtension: any): void {
189191
}
190192
}
191193

194+
/*
195+
* This patch extends the existing upstream extension for Kinesis. Extensions allow for custom logic for adding
196+
* service-specific information to spans, such as attributes. Specifically, we are adding logic to add
197+
* `aws.kinesis.stream.arn` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation.
198+
*
199+
*
200+
* @param kinesisServiceExtension Kinesis Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation
201+
*/
202+
function patchKinesisServiceExtension(kinesisServiceExtension: any): void {
203+
if (kinesisServiceExtension) {
204+
const requestPreSpanHook = kinesisServiceExtension.requestPreSpanHook;
205+
kinesisServiceExtension._requestPreSpanHook = requestPreSpanHook;
206+
207+
const patchedRequestPreSpanHook = (
208+
request: NormalizedRequest,
209+
_config: AwsSdkInstrumentationConfig
210+
): RequestMetadata => {
211+
const requestMetadata: RequestMetadata = kinesisServiceExtension._requestPreSpanHook(request, _config);
212+
if (requestMetadata.spanAttributes) {
213+
const streamArn = request.commandInput?.StreamARN;
214+
if (streamArn) {
215+
requestMetadata.spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_ARN] = streamArn;
216+
}
217+
}
218+
return requestMetadata;
219+
};
220+
221+
kinesisServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook;
222+
}
223+
}
224+
225+
/*
226+
* This patch extends the existing upstream extension for DynamoDB. Extensions allow for custom logic for adding
227+
* service-specific information to spans, such as attributes. Specifically, we are adding logic to add
228+
* `aws.dynamodb.table.arn` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation.
229+
*
230+
*
231+
* @param dynamoDbServiceExtension DynamoDB Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation
232+
*/
233+
function patchDynamoDbServiceExtension(dynamoDbServiceExtension: any): void {
234+
if (dynamoDbServiceExtension) {
235+
if (typeof dynamoDbServiceExtension.responseHook === 'function') {
236+
const responseHook = dynamoDbServiceExtension.responseHook;
237+
238+
const patchedResponseHook = (
239+
response: NormalizedResponse,
240+
span: Span,
241+
tracer: Tracer,
242+
config: AwsSdkInstrumentationConfig
243+
): void => {
244+
responseHook.call(dynamoDbServiceExtension, response, span, tracer, config);
245+
246+
const tableArn = response?.data?.Table?.TableArn;
247+
if (tableArn) {
248+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_ARN, tableArn);
249+
}
250+
};
251+
252+
dynamoDbServiceExtension.responseHook = patchedResponseHook;
253+
}
254+
}
255+
}
256+
192257
/*
193258
* This patch extends the existing upstream extension for Lambda. Extensions allow for custom logic for adding
194259
* service-specific information to spans, such as attributes. Specifically, we are adding logic to add
@@ -293,7 +358,7 @@ function patchAwsLambdaInstrumentation(instrumentation: Instrumentation): void {
293358
}
294359
}
295360

296-
// Override the upstream private _getV3SmithyClientSendPatch method to add middleware to inject X-Ray Trace Context into HTTP Headers
361+
// Override the upstream private _getV3SmithyClientSendPatch method to add middlewares to inject X-Ray Trace Context into HTTP Headers and to extract account access key id and region for cross-account support
297362
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/instrumentation-aws-sdk-v0.48.0/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts#L373-L384
298363
const awsXrayPropagator = new AWSXRayPropagator();
299364
const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config');
@@ -328,6 +393,40 @@ function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
328393
}
329394
);
330395

396+
this.middlewareStack?.add(
397+
(next: any, context: any) => async (middlewareArgs: any) => {
398+
const activeContext = otelContext.active();
399+
const span = trace.getSpan(activeContext);
400+
401+
if (span) {
402+
try {
403+
const credsProvider = this.config.credentials;
404+
if (credsProvider instanceof Function) {
405+
const credentials = await credsProvider();
406+
if (credentials?.accessKeyId) {
407+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
408+
}
409+
}
410+
if (this.config.region instanceof Function) {
411+
const region = await this.config.region();
412+
if (region) {
413+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
414+
}
415+
}
416+
} catch (err) {
417+
diag.debug('Failed to get auth account access key and region:', err);
418+
}
419+
}
420+
421+
return await next(middlewareArgs);
422+
},
423+
{
424+
step: 'build',
425+
name: '_adotExtractSignerCredentials',
426+
override: true,
427+
}
428+
);
429+
331430
command[V3_CLIENT_CONFIG_KEY] = this.config;
332431
return original.apply(this, [command, ...args]);
333432
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { AttributeValue } from '@opentelemetry/api';
5+
import { isAccountId } from './utils';
6+
7+
export class RegionalResourceArnParser {
8+
/** Parses ARN with formats:
9+
* arn:partition:service:region:account-id:resource-type/resource-id or
10+
* arn:partition:service:region:account-id:resource-type:resource-id
11+
*/
12+
private static parseArn(arn: AttributeValue | undefined): string[] | undefined {
13+
if (typeof arn !== 'string') return undefined;
14+
const parts = arn.split(':');
15+
return parts.length >= 6 && parts[0] === 'arn' && isAccountId(parts[4]) ? parts : undefined;
16+
}
17+
18+
public static getAccountId(arn: AttributeValue | undefined): string | undefined {
19+
return this.parseArn(arn)?.[4];
20+
}
21+
22+
public static getRegion(arn: AttributeValue | undefined): string | undefined {
23+
return this.parseArn(arn)?.[3];
24+
}
25+
26+
public static extractDynamoDbTableNameFromArn(arn: AttributeValue | undefined): string | undefined {
27+
return this.extractResourceNameFromArn(arn)?.replace('table/', '');
28+
}
29+
30+
public static extractKinesisStreamNameFromArn(arn: AttributeValue | undefined): string | undefined {
31+
return this.extractResourceNameFromArn(arn)?.replace('stream/', '');
32+
}
33+
34+
public static extractResourceNameFromArn(arn: AttributeValue | undefined): string | undefined {
35+
const parts = this.parseArn(arn);
36+
return parts?.[parts.length - 1];
37+
}
38+
}

0 commit comments

Comments
 (0)