Skip to content

Commit 214b938

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 7151d66 commit 214b938

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
@@ -81,6 +81,8 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[])
8181
patchSqsServiceExtension(services.get('SQS'));
8282
patchSnsServiceExtension(services.get('SNS'));
8383
patchLambdaServiceExtension(services.get('Lambda'));
84+
patchKinesisServiceExtension(services.get('Kinesis'));
85+
patchDynamoDbServiceExtension(services.get('DynamoDB'));
8486
}
8587
} else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') {
8688
diag.debug('Patching aws lambda instrumentation');
@@ -190,6 +192,69 @@ function patchSnsServiceExtension(snsServiceExtension: any): void {
190192
}
191193
}
192194

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

349-
// Override the upstream private _getV3SmithyClientSendPatch method to add middleware to inject X-Ray Trace Context into HTTP Headers
414+
// 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
350415
// 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
351416
const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config');
352417
type V3PluginCommand = AwsV3Command<any, any, any, any, any> & {
@@ -380,6 +445,40 @@ function patchAwsSdkInstrumentation(instrumentation: Instrumentation): void {
380445
}
381446
);
382447

448+
this.middlewareStack?.add(
449+
(next: any, context: any) => async (middlewareArgs: any) => {
450+
const activeContext = otelContext.active();
451+
const span = trace.getSpan(activeContext);
452+
453+
if (span) {
454+
try {
455+
const credsProvider = this.config.credentials;
456+
if (credsProvider instanceof Function) {
457+
const credentials = await credsProvider();
458+
if (credentials?.accessKeyId) {
459+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_ACCOUNT_ACCESS_KEY, credentials.accessKeyId);
460+
}
461+
}
462+
if (this.config.region instanceof Function) {
463+
const region = await this.config.region();
464+
if (region) {
465+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_AUTH_REGION, region);
466+
}
467+
}
468+
} catch (err) {
469+
diag.debug('Failed to get auth account access key and region:', err);
470+
}
471+
}
472+
473+
return await next(middlewareArgs);
474+
},
475+
{
476+
step: 'build',
477+
name: '_adotExtractSignerCredentials',
478+
override: true,
479+
}
480+
);
481+
383482
command[V3_CLIENT_CONFIG_KEY] = this.config;
384483
return original.apply(this, [command, ...args]);
385484
};
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)