From 694e4f4b5c48113c9a2624d058a80f5de27e2452 Mon Sep 17 00:00:00 2001 From: Michael He <53622546+yiyuan-he@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:45:07 -0800 Subject: [PATCH 1/7] Update `gen_ai.system` attribute key (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *Description of changes:* Updating `gen_ai.system` attribute key to better align with upstream Otel conventions. Context: https://github.com/open-telemetry/semantic-conventions/pull/1574#issuecomment-2539951918 *Test plan:* Ran updated unit tests and contract tests. Screenshot 2024-12-16 at 9 57 36 AM By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- .../src/patches/aws/services/bedrock.ts | 2 +- .../test/patches/aws/services/bedrock.test.ts | 14 +++++++------- .../test/patches/instrumentation-patch.test.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts index 0072900..4f2736e 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts @@ -16,7 +16,7 @@ const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId'; const DATA_SOURCE_ID: string = 'dataSourceId'; const GUARDRAIL_ID: string = 'guardrailId'; const MODEL_ID: string = 'modelId'; -const AWS_BEDROCK_SYSTEM: string = 'aws_bedrock'; +const AWS_BEDROCK_SYSTEM: string = 'aws.bedrock'; const AGENT_OPERATIONS = [ 'CreateAgentActionGroup', diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts index 8cc5a2b..b1516c6 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts @@ -331,7 +331,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(512); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.6); @@ -384,7 +384,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(4096); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0); @@ -448,7 +448,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(1000); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(1.0); @@ -506,7 +506,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(512); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.5); @@ -560,7 +560,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(512); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.5); @@ -614,7 +614,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(512); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.5); @@ -676,7 +676,7 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); - expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws_bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(4096); expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.75); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts index 385c7d7..fe946d6 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts @@ -32,7 +32,7 @@ const _BEDROCK_AGENT_ID: string = 'agentId'; const _BEDROCK_DATASOURCE_ID: string = 'DataSourceId'; const _BEDROCK_GUARDRAIL_ID: string = 'GuardrailId'; const _BEDROCK_KNOWLEDGEBASE_ID: string = 'KnowledgeBaseId'; -const _GEN_AI_SYSTEM: string = 'aws_bedrock'; +const _GEN_AI_SYSTEM: string = 'aws.bedrock'; const _GEN_AI_REQUEST_MODEL: string = 'genAiReuqestModelId'; const mockHeaders = { From a9375ffd42e01bcdf96e2cf19b0687c2706063d2 Mon Sep 17 00:00:00 2001 From: Jonathan Lee <107072447+jj22ee@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:08:50 -0800 Subject: [PATCH 2/7] [Fix] Update AWS SDK Instrumentation to inject XRay trace context into HTTP Headers (#131) *Issue #, if available:* Fixes the absence of broken X-Ray context propagation when the underlying HTTP instrumentation is suppressed or disabled. Similarly to Java and Python, AWS SDK Js instrumentation itself should be able to inject the X-Ray Context into the HTTP Headers: - [Java example](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/81c7713bb2f638c85006c3e152ad13d6e02f3259/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/TracingExecutionInterceptor.java#L308) - [Python example ](https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py#L163-L165) - See Specification that clarifies that AWS SDK instrumentations should use X-Ray propagator specifically. - https://github.com/open-telemetry/opentelemetry-specification/blob/v1.40.0/supplementary-guidelines/compatibility/aws.md?plain=1#L9-L12 Note - If the underlying HTTP instrumentation is enabled, then the underlying HTTP Child Span of the AWS SDK Span will overwrite the Trace Context to propagate through headers. *Description of changes:* - Move patched/extended instrumentations to an `patches/extended-instrumentations/` directory - Created `AwsSdkInstrumentationExtended` class that extends upstream AwsInstrumentation to override its patching mechanism of the `send` method. The overridden method will additionally update the AWS SDK middleware stack to inject the `X-Amzn-Trace-Id` HTTP header. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- .../.eslintignore | 3 +- .../package.json | 11 +++- .../aws-lambda.ts | 0 .../aws-sdk-instrumentation-extended.ts | 42 ++++++++++++++ .../src/patches/instrumentation-patch.ts | 17 +++++- .../src/register.ts | 2 +- .../aws-lambda.test.ts | 2 +- .../aws-sdk-instrumentation-extended.test.ts | 48 +++++++++++++++ .../patches/instrumentation-patch.test.ts | 58 +++++++++++++++++++ 9 files changed, 176 insertions(+), 7 deletions(-) rename aws-distro-opentelemetry-node-autoinstrumentation/src/patches/{aws/services => extended-instrumentations}/aws-lambda.ts (100%) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-sdk-instrumentation-extended.ts rename aws-distro-opentelemetry-node-autoinstrumentation/test/patches/{aws/services => extended-instrumentations}/aws-lambda.test.ts (98%) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-sdk-instrumentation-extended.test.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/.eslintignore b/aws-distro-opentelemetry-node-autoinstrumentation/.eslintignore index 34c98dc..8cbd431 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/.eslintignore +++ b/aws-distro-opentelemetry-node-autoinstrumentation/.eslintignore @@ -1,4 +1,5 @@ build node_modules .eslintrc.js -version.ts \ No newline at end of file +version.ts +src/third-party \ No newline at end of file diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index 38b1d2a..4be5456 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -31,9 +31,18 @@ "prepublishOnly": "npm run compile", "tdd": "yarn test -- --watch-extensions ts --watch", "test": "nyc ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'", - "test:coverage": "nyc --all --check-coverage --functions 95 --lines 95 ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'", + "test:coverage": "nyc --check-coverage --functions 95 --lines 95 ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'", "watch": "tsc -w" }, + "nyc": { + "all": true, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/third-party/**/*.ts" + ] + }, "bugs": { "url": "https://github.com/aws-observability/aws-otel-js-instrumentation/issues" }, diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/aws-lambda.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-lambda.ts similarity index 100% rename from aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/aws-lambda.ts rename to aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-lambda.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-sdk-instrumentation-extended.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-sdk-instrumentation-extended.ts new file mode 100644 index 0000000..b3044d6 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/extended-instrumentations/aws-sdk-instrumentation-extended.ts @@ -0,0 +1,42 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'; +import { context as otelContext, defaultTextMapSetter } from '@opentelemetry/api'; +import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray'; +import type { Command as AwsV3Command } from '@aws-sdk/types'; + +const awsXrayPropagator = new AWSXRayPropagator(); +const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config'); +type V3PluginCommand = AwsV3Command & { + [V3_CLIENT_CONFIG_KEY]?: any; +}; + +// This class extends the upstream AwsInstrumentation to override its patching mechanism of the `send` method. +// The overriden method will additionally update the AWS SDK middleware stack to inject the `X-Amzn-Trace-Id` HTTP header. +// +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +export class AwsSdkInstrumentationExtended extends AwsInstrumentation { + // Override the upstream private _getV3SmithyClientSendPatch method to add middleware to inject X-Ray Trace Context into HTTP Headers + // 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 + override _getV3SmithyClientSendPatch(original: (...args: unknown[]) => Promise) { + return function send(this: any, command: V3PluginCommand, ...args: unknown[]): Promise { + this.middlewareStack?.add( + (next: any, context: any) => async (middlewareArgs: any) => { + awsXrayPropagator.inject(otelContext.active(), middlewareArgs.request.headers, defaultTextMapSetter); + const result = await next(middlewareArgs); + return result; + }, + { + step: 'build', + name: '_adotInjectXrayContextMiddleware', + override: true, + } + ); + + command[V3_CLIENT_CONFIG_KEY] = this.config; + return original.apply(this, [command, ...args]); + }; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts index 7cc98bd..1302bb7 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts @@ -25,7 +25,9 @@ import { } from './aws/services/bedrock'; import { KinesisServiceExtension } from './aws/services/kinesis'; import { S3ServiceExtension } from './aws/services/s3'; -import { AwsLambdaInstrumentationPatch } from './aws/services/aws-lambda'; +import { AwsLambdaInstrumentationPatch } from './extended-instrumentations/aws-lambda'; +import { InstrumentationConfigMap } from '@opentelemetry/auto-instrumentations-node'; +import { AwsSdkInstrumentationExtended } from './extended-instrumentations/aws-sdk-instrumentation-extended'; export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID'; const awsPropagator = new AWSXRayPropagator(); @@ -38,7 +40,10 @@ export const headerGetter: TextMapGetter = { }, }; -export function applyInstrumentationPatches(instrumentations: Instrumentation[]): void { +export function applyInstrumentationPatches( + instrumentations: Instrumentation[], + instrumentationConfigs?: InstrumentationConfigMap +): void { /* Apply patches to upstream instrumentation libraries. @@ -50,10 +55,16 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[]) */ instrumentations.forEach((instrumentation, index) => { if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-sdk') { + diag.debug('Overriding aws sdk instrumentation'); + instrumentations[index] = new AwsSdkInstrumentationExtended( + instrumentationConfigs ? instrumentationConfigs['@opentelemetry/instrumentation-aws-sdk'] : undefined + ); + // Access private property servicesExtensions of AwsInstrumentation // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const services: Map | undefined = (instrumentation as any).servicesExtensions?.services; + const services: Map | undefined = (instrumentations[index] as any).servicesExtensions + ?.services; if (services) { services.set('S3', new S3ServiceExtension()); services.set('Kinesis', new KinesisServiceExtension()); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts index 823f164..751c0ff 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts @@ -62,7 +62,7 @@ const instrumentationConfigs: InstrumentationConfigMap = { const instrumentations: Instrumentation[] = getNodeAutoInstrumentations(instrumentationConfigs); // Apply instrumentation patches -applyInstrumentationPatches(instrumentations); +applyInstrumentationPatches(instrumentations, instrumentationConfigs); const configurator: AwsOpentelemetryConfigurator = new AwsOpentelemetryConfigurator(instrumentations, useXraySampler); const configuration: Partial = configurator.configure(); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/aws-lambda.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-lambda.test.ts similarity index 98% rename from aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/aws-lambda.test.ts rename to aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-lambda.test.ts index d68e1e6..0580769 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/aws-lambda.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-lambda.test.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { diag } from '@opentelemetry/api'; import { InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; -import { AwsLambdaInstrumentationPatch } from '../../../../src/patches/aws/services/aws-lambda'; +import { AwsLambdaInstrumentationPatch } from '../../../src/patches/extended-instrumentations/aws-lambda'; describe('AwsLambdaInstrumentationPatch', () => { let instrumentation: AwsLambdaInstrumentationPatch; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-sdk-instrumentation-extended.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-sdk-instrumentation-extended.test.ts new file mode 100644 index 0000000..9751d21 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/extended-instrumentations/aws-sdk-instrumentation-extended.test.ts @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as sinon from 'sinon'; +import { AwsSdkInstrumentationExtended } from '../../../src/patches/extended-instrumentations/aws-sdk-instrumentation-extended'; +import expect from 'expect'; +import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray'; +import { Context, TextMapSetter } from '@opentelemetry/api'; + +describe('AwsSdkInstrumentationExtended', () => { + let instrumentation: AwsSdkInstrumentationExtended; + + beforeEach(() => { + instrumentation = new AwsSdkInstrumentationExtended({}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('overridden _getV3SmithyClientSendPatch updates MiddlewareStack', async () => { + const mockedMiddlewareStackInternal: any = []; + const mockedMiddlewareStack = { + add: (arg1: any, arg2: any) => mockedMiddlewareStackInternal.push([arg1, arg2]), + }; + const send = instrumentation + ._getV3SmithyClientSendPatch((...args: unknown[]) => Promise.resolve()) + .bind({ middlewareStack: mockedMiddlewareStack }); + sinon + .stub(AWSXRayPropagator.prototype, 'inject') + .callsFake((context: Context, carrier: unknown, setter: TextMapSetter) => { + (carrier as any)['isCarrierModified'] = 'carrierIsModified'; + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await send({}, null); + + const middlewareArgs: any = { + request: { + headers: {}, + }, + }; + await mockedMiddlewareStackInternal[0][0]((arg: any) => Promise.resolve(), null)(middlewareArgs); + + expect(middlewareArgs.request.headers['isCarrierModified']).toEqual('carrierIsModified'); + expect(mockedMiddlewareStackInternal[0][1].name).toEqual('_adotInjectXrayContextMiddleware'); + }); +}); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts index fe946d6..9c11b06 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts @@ -23,6 +23,11 @@ import * as sinon from 'sinon'; import { AWSXRAY_TRACE_ID_HEADER, AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray'; import { Context } from 'aws-lambda'; import { SinonStub } from 'sinon'; +import { S3 } from '@aws-sdk/client-s3'; +import nock = require('nock'); +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { getTestSpans, registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils'; +import { AwsSdkInstrumentationExtended } from '../../src/patches/extended-instrumentations/aws-sdk-instrumentation-extended'; const _STREAM_NAME: string = 'streamName'; const _BUCKET_NAME: string = 'bucketName'; @@ -45,6 +50,10 @@ const UNPATCHED_INSTRUMENTATIONS: Instrumentation[] = getNodeAutoInstrumentation const PATCHED_INSTRUMENTATIONS: Instrumentation[] = getNodeAutoInstrumentations(); applyInstrumentationPatches(PATCHED_INSTRUMENTATIONS); +const extendedAwsSdkInstrumentation: AwsInstrumentation = new AwsInstrumentation(); +applyInstrumentationPatches([extendedAwsSdkInstrumentation]); +registerInstrumentationTesting(extendedAwsSdkInstrumentation); + describe('InstrumentationPatchTest', () => { it('SanityTestUnpatchedAwsSdkInstrumentation', () => { const awsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); @@ -89,6 +98,9 @@ describe('InstrumentationPatchTest', () => { expect(services.get('BedrockRuntime')).toBeTruthy(); // Sanity check expect(services.has('InvalidService')).toBeFalsy(); + + // Check that the original AWS SDK Instrumentation is replaced with the extended version + expect(awsSdkInstrumentation).toBeInstanceOf(AwsSdkInstrumentationExtended); }); it('S3 without patching', () => { @@ -388,6 +400,52 @@ describe('InstrumentationPatchTest', () => { expect(filteredInstrumentations.length).toEqual(1); return filteredInstrumentations[0] as AwsLambdaInstrumentation; } + + describe('AwsSdkInstrumentationPatchTest', () => { + let s3: S3; + const region = 'us-east-1'; + + it('injects trace context header into request via propagator', async () => { + s3 = new S3({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + + const dummyBucketName: string = 'dummy-bucket-name'; + let reqHeaders: any = {}; + + nock(`https://${dummyBucketName}.s3.${region}.amazonaws.com`) + .get('/') + .reply(200, function (uri: any, requestBody: any) { + reqHeaders = this.req.headers; + return 'null'; + }); + + await s3 + .listObjects({ + Bucket: dummyBucketName, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const listObjectsSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'S3.ListObjects'; + }); + + expect(listObjectsSpans.length).toBe(1); + + const traceId = listObjectsSpans[0].spanContext().traceId; + const spanId = listObjectsSpans[0].spanContext().spanId; + expect(reqHeaders['x-amzn-trace-id'] as string).toEqual( + `Root=1-${traceId.substring(0, 8)}-${listObjectsSpans[0] + .spanContext() + .traceId.substring(8, 32)};Parent=${spanId};Sampled=1` + ); + }); + }); }); describe('customExtractor', () => { From c58112eb99bb7efcc87f8aa2e6836968e788a0a2 Mon Sep 17 00:00:00 2001 From: Steve Liu Date: Mon, 16 Dec 2024 14:47:20 -0800 Subject: [PATCH 3/7] feat: [.NET and JS Feature Parity] Support for new AWS Resources in JS SDK (#121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *Description of changes:* Adding support for new AWS resources in JS SDK. Part of an ongoing project to increase the ADOT SDK support in Node and .NET. Changes in this PR support the exact same features as the Python version: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/265 Manual Testing: Screenshot 2024-11-15 at 1 36 01 PM Screenshot 2024-11-15 at 2 07 05 PM Screenshot 2024-11-15 at 2 26 10 PM Screenshot 2024-11-18 at 9 47 33 AM Screenshot 2024-11-18 at 9 57 22 AM Screenshot 2024-11-18 at 10 00 34 AM Unit Tests for Instrumentation Patches and Metric Attribute Generators: image By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- .../package.json | 4 + .../src/aws-attribute-keys.ts | 9 + .../src/aws-metric-attribute-generator.ts | 77 +- .../src/patches/aws/services/bedrock.ts | 13 +- .../patches/aws/services/secretsmanager.ts | 43 + .../patches/aws/services/step-functions.ts | 36 + .../src/patches/instrumentation-patch.ts | 70 +- .../aws-metric-attribute-generator.test.ts | 37 + .../test/patches/aws/services/bedrock.test.ts | 5 +- .../aws/services/secretsmanager.test.ts | 100 ++ .../aws/services/step-functions.test.ts | 93 ++ .../patches/instrumentation-patch.test.ts | 177 ++- .../images/applications/mysql2/package.json | 2 +- package-lock.json | 1200 ++++++++++++++--- 14 files changed, 1641 insertions(+), 225 deletions(-) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/secretsmanager.ts create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/step-functions.ts create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/secretsmanager.test.ts create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/step-functions.test.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index 4be5456..78412fa 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -73,7 +73,11 @@ "@aws-sdk/client-bedrock-agent-runtime": "3.632.0", "@aws-sdk/client-bedrock-runtime": "3.632.0", "@aws-sdk/client-kinesis": "3.632.0", + "@aws-sdk/client-lambda": "^3.632.0", "@aws-sdk/client-s3": "3.632.0", + "@aws-sdk/client-secrets-manager": "^3.632.0", + "@aws-sdk/client-sfn": "^3.632.0", + "@aws-sdk/client-sns": "^3.632.0", "@opentelemetry/contrib-test-utils": "0.41.0", "@types/mocha": "7.0.2", "@types/node": "18.6.5", diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts index e237827..46150b7 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts @@ -14,6 +14,7 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = { AWS_REMOTE_RESOURCE_IDENTIFIER: 'aws.remote.resource.identifier', AWS_SDK_DESCENDANT: 'aws.sdk.descendant', AWS_CONSUMER_PARENT_SPAN_KIND: 'aws.consumer.parent.span.kind', + AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER: 'aws.remote.resource.cfn.primary.identifier', AWS_REMOTE_TARGET: 'aws.remote.target', AWS_REMOTE_DB_USER: 'aws.remote.db.user', @@ -35,4 +36,12 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = { AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id', AWS_BEDROCK_AGENT_ID: 'aws.bedrock.agent.id', AWS_BEDROCK_GUARDRAIL_ID: 'aws.bedrock.guardrail.id', + AWS_BEDROCK_GUARDRAIL_ARN: 'aws.bedrock.guardrail.arn', + AWS_SNS_TOPIC_ARN: 'aws.sns.topic.arn', + AWS_SECRETSMANAGER_SECRET_ARN: 'aws.secretsmanager.secret.arn', + AWS_STEPFUNCTIONS_STATEMACHINE_ARN: 'aws.stepfunctions.state_machine.arn', + AWS_STEPFUNCTIONS_ACTIVITY_ARN: 'aws.stepfunctions.activity.arn', + AWS_LAMBDA_FUNCTION_NAME: 'aws.lambda.function.name', + AWS_LAMBDA_RESOURCE_MAPPING_ID: 'aws.lambda.resource_mapping.id', + AWS_LAMBDA_FUNCTION_ARN: 'aws.lambda.function.arn', }; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts index e58b600..ae318a3 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts @@ -55,6 +55,10 @@ const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB'; const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis'; const NORMALIZED_S3_SERVICE_NAME: string = 'AWS::S3'; const NORMALIZED_SQS_SERVICE_NAME: string = 'AWS::SQS'; +const NORMALIZED_SNS_SERVICE_NAME: string = 'AWS::SNS'; +const NORMALIZED_SECRETSMANAGER_SERVICE_NAME = 'AWS::SecretsManager'; +const NORMALIZED_STEPFUNCTIONS_SERVICE_NAME = 'AWS::StepFunctions'; +const NORMALIZED_LAMBDA_SERVICE_NAME = 'AWS::Lambda'; const NORMALIZED_BEDROCK_SERVICE_NAME: string = 'AWS::Bedrock'; const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: string = 'AWS::BedrockRuntime'; @@ -330,6 +334,8 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { BedrockAgent: NORMALIZED_BEDROCK_SERVICE_NAME, BedrockAgentRuntime: NORMALIZED_BEDROCK_SERVICE_NAME, BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME, + SecretsManager: NORMALIZED_SECRETSMANAGER_SERVICE_NAME, + SFN: NORMALIZED_STEPFUNCTIONS_SERVICE_NAME, }; return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName; } @@ -350,6 +356,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { private static setRemoteResourceTypeAndIdentifier(span: ReadableSpan, attributes: Attributes): void { let remoteResourceType: AttributeValue | undefined; let remoteResourceIdentifier: AttributeValue | undefined; + let cloudFormationIdentifier: AttributeValue | undefined; if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) { const awsTableNames: AttributeValue | undefined = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES]; @@ -370,16 +377,56 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( span.attributes[AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET] ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN)) { + const snsArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN]; + + remoteResourceType = NORMALIZED_SNS_SERVICE_NAME + '::Topic'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + this.extractResourceNameFromArn(snsArn) + ); + cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(snsArn); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN)) { + const secretsArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]; + + remoteResourceType = NORMALIZED_SECRETSMANAGER_SERVICE_NAME + '::Secret'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + this.extractResourceNameFromArn(secretsArn) + ); + cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(secretsArn); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN)) { + const stateMachineArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN]; + + remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::StateMachine'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + this.extractResourceNameFromArn(stateMachineArn) + ); + cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(stateMachineArn); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN)) { + const activityArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN]; + + remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::Activity'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + this.extractResourceNameFromArn(activityArn) + ); + cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(activityArn); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID)) { + remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::EventSourceMapping'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID] + ); } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME)) { remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME] ); } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL)) { - remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue'; - remoteResourceIdentifier = SqsUrlParser.getQueueName( - AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]) + const sqsQueueUrl = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL] ); + + remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue'; + remoteResourceIdentifier = SqsUrlParser.getQueueName(sqsQueueUrl); + cloudFormationIdentifier = sqsQueueUrl; } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( @@ -390,11 +437,17 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID] ); + cloudFormationIdentifier = `${AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID] + )}|${remoteResourceIdentifier}`; } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID] ); + cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN] + ); } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( @@ -414,6 +467,14 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { if (remoteResourceType !== undefined && remoteResourceIdentifier !== undefined) { attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType; attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier; + + if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) { + if (cloudFormationIdentifier === undefined) { + cloudFormationIdentifier = remoteResourceIdentifier; + } + + attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier; + } } } @@ -532,6 +593,16 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { return input.split('^').join('^^').split('|').join('^|'); } + // Extracts the name of the resource from an arn + private static extractResourceNameFromArn(attribute: AttributeValue | undefined): string | undefined { + if (typeof attribute === 'string' && attribute.startsWith('arn:aws:')) { + const split = attribute.split(':'); + return split[split.length - 1]; + } + + return undefined; + } + /** Span kind is needed for differentiating metrics in the EMF exporter */ private static setSpanKindForService(span: ReadableSpan, attributes: Attributes): void { let spanKind: string = SpanKind[span.kind]; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts index 4f2736e..82da47a 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts @@ -15,6 +15,7 @@ const AGENT_ID: string = 'agentId'; const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId'; const DATA_SOURCE_ID: string = 'dataSourceId'; const GUARDRAIL_ID: string = 'guardrailId'; +const GUARDRAIL_ARN: string = 'guardrailArn'; const MODEL_ID: string = 'modelId'; const AWS_BEDROCK_SYSTEM: string = 'aws.bedrock'; @@ -60,6 +61,7 @@ const knowledgeBaseOperationAttributeKeyMapping = { }; const dataSourceOperationAttributeKeyMapping = { [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]: DATA_SOURCE_ID, + [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]: KNOWLEDGE_BASE_ID, }; // This map allows us to get all relevant attribute key mappings for a given operation @@ -182,10 +184,15 @@ export class BedrockServiceExtension implements ServiceExtension { }; } responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { - const guardrail_id = response.data[GUARDRAIL_ID]; + const guardrailId = response.data[GUARDRAIL_ID]; + const guardrailArn = response.data[GUARDRAIL_ARN]; + + if (guardrailId) { + span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrailId); + } - if (guardrail_id) { - span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrail_id); + if (guardrailArn) { + span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN, guardrailArn); } } } diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/secretsmanager.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/secretsmanager.ts new file mode 100644 index 0000000..bf78434 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/secretsmanager.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api'; +import { + AwsSdkInstrumentationConfig, + NormalizedRequest, + NormalizedResponse, +} from '@opentelemetry/instrumentation-aws-sdk'; +import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys'; +import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension'; + +export class SecretsManagerServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const secretId = request.commandInput?.SecretId; + + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const spanAttributes: Attributes = {}; + + if (typeof secretId === 'string' && secretId.startsWith('arn:aws:secretsmanager:')) { + spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN] = secretId; + } + + const isIncoming = false; + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } + + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { + const secretArn = response.data.ARN; + + if (secretArn) { + span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN, secretArn); + } + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/step-functions.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/step-functions.ts new file mode 100644 index 0000000..4271cb0 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/step-functions.ts @@ -0,0 +1,36 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Attributes, SpanKind } from '@opentelemetry/api'; +import { AwsSdkInstrumentationConfig, NormalizedRequest } from '@opentelemetry/instrumentation-aws-sdk'; +import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys'; +import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension'; + +export class StepFunctionsServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const stateMachineArn = request.commandInput?.stateMachineArn; + const activityArn = request.commandInput?.activityArn; + + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const spanAttributes: Attributes = {}; + + if (stateMachineArn) { + spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN] = stateMachineArn; + } + + if (activityArn) { + spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN] = activityArn; + } + + const isIncoming = false; + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts index 1302bb7..74879b9 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts @@ -25,9 +25,11 @@ import { } from './aws/services/bedrock'; import { KinesisServiceExtension } from './aws/services/kinesis'; import { S3ServiceExtension } from './aws/services/s3'; -import { AwsLambdaInstrumentationPatch } from './extended-instrumentations/aws-lambda'; +import { SecretsManagerServiceExtension } from './aws/services/secretsmanager'; +import { StepFunctionsServiceExtension } from './aws/services/step-functions'; import { InstrumentationConfigMap } from '@opentelemetry/auto-instrumentations-node'; import { AwsSdkInstrumentationExtended } from './extended-instrumentations/aws-sdk-instrumentation-extended'; +import { AwsLambdaInstrumentationPatch } from './extended-instrumentations/aws-lambda'; export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID'; const awsPropagator = new AWSXRayPropagator(); @@ -68,11 +70,15 @@ export function applyInstrumentationPatches( if (services) { services.set('S3', new S3ServiceExtension()); services.set('Kinesis', new KinesisServiceExtension()); + services.set('SecretsManager', new SecretsManagerServiceExtension()); + services.set('SFN', new StepFunctionsServiceExtension()); services.set('Bedrock', new BedrockServiceExtension()); services.set('BedrockAgent', new BedrockAgentServiceExtension()); services.set('BedrockAgentRuntime', new BedrockAgentRuntimeServiceExtension()); services.set('BedrockRuntime', new BedrockRuntimeServiceExtension()); patchSqsServiceExtension(services.get('SQS')); + patchSnsServiceExtension(services.get('SNS')); + patchLambdaServiceExtension(services.get('Lambda')); } } else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') { diag.debug('Overriding aws lambda instrumentation'); @@ -154,3 +160,65 @@ function patchSqsServiceExtension(sqsServiceExtension: any): void { sqsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; } } + +/* + * This patch extends the existing upstream extension for SNS. Extensions allow for custom logic for adding + * service-specific information to spans, such as attributes. Specifically, we are adding logic to add + * `aws.sns.topic.arn` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation. + * + * + * @param snsServiceExtension SNS Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation + */ +function patchSnsServiceExtension(snsServiceExtension: any): void { + if (snsServiceExtension) { + const requestPreSpanHook = snsServiceExtension.requestPreSpanHook; + snsServiceExtension._requestPreSpanHook = requestPreSpanHook; + + const patchedRequestPreSpanHook = ( + request: NormalizedRequest, + _config: AwsSdkInstrumentationConfig + ): RequestMetadata => { + const requestMetadata: RequestMetadata = snsServiceExtension._requestPreSpanHook(request, _config); + if (requestMetadata.spanAttributes) { + const topicArn = request.commandInput?.TopicArn; + if (topicArn) { + requestMetadata.spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN] = topicArn; + } + } + return requestMetadata; + }; + + snsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; + } +} + +/* + * This patch extends the existing upstream extension for Lambda. Extensions allow for custom logic for adding + * service-specific information to spans, such as attributes. Specifically, we are adding logic to add + * `aws.lambda.resource_mapping.id` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation. + * + * + * @param lambdaServiceExtension Lambda Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation + */ +function patchLambdaServiceExtension(lambdaServiceExtension: any): void { + if (lambdaServiceExtension) { + const requestPreSpanHook = lambdaServiceExtension.requestPreSpanHook; + lambdaServiceExtension._requestPreSpanHook = requestPreSpanHook; + + const patchedRequestPreSpanHook = ( + request: NormalizedRequest, + _config: AwsSdkInstrumentationConfig + ): RequestMetadata => { + const requestMetadata: RequestMetadata = lambdaServiceExtension._requestPreSpanHook(request, _config); + if (requestMetadata.spanAttributes) { + const resourceMappingId = request.commandInput?.UUID; + if (resourceMappingId) { + requestMetadata.spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID] = resourceMappingId; + } + } + return requestMetadata; + }; + + lambdaServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts index e74eda3..0e0a7f2 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts @@ -761,6 +761,39 @@ describe('AwsMetricAttributeGeneratorTest', () => { validateRemoteResourceAttributes('AWS::Kinesis::Stream', 'AWS_KINESIS_STREAM_NAME'); mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME, undefined); + // Validate behaviour of AWS_SNS_TOPIC_ARN attribute then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN, 'arn:aws:sns:us-east-1:123456789012:testTopic'); + validateRemoteResourceAttributes('AWS::SNS::Topic', 'testTopic'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN, undefined); + + // Validate behaviour of AWS_SECRETSMANAGER_SECRET_ARN attributes then remove it. + mockAttribute( + AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN, + 'arn:aws:secretsmanager:us-east-1:123456789123:secret:testSecret' + ); + validateRemoteResourceAttributes('AWS::SecretsManager::Secret', 'testSecret'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN, undefined); + + // Validate behaviour of AWS_LAMBDA_RESOURCE_MAPPING_ID attribute then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID, 'aws_lambda_resource_mapping_id'); + validateRemoteResourceAttributes('AWS::Lambda::EventSourceMapping', 'aws_lambda_resource_mapping_id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID, undefined); + + // Validate behaviour of AWS_STEPFUNCTIONS_STATEMACHINE_ARN and AWS_STEPFUNCTIONS_ACTIVITY_ARN attributes then remove them. + mockAttribute( + AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN, + 'arn:aws:states:us-east-1:123456789123:stateMachine:testStateMachine' + ); + validateRemoteResourceAttributes('AWS::StepFunctions::StateMachine', 'testStateMachine'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN, undefined); + + mockAttribute( + AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN, + 'arn:aws:states:us-east-1:123456789123:activity:testActivity' + ); + validateRemoteResourceAttributes('AWS::StepFunctions::Activity', 'testActivity'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN, undefined); + // Validate behaviour of AWS_TABLE_NAMES attribute with one table name, then remove it. mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES, ['aws_table_name']); validateRemoteResourceAttributes('AWS::DynamoDB::Table', 'aws_table_name'); @@ -1167,6 +1200,10 @@ describe('AwsMetricAttributeGeneratorTest', () => { testAwsSdkServiceNormalization('Kinesis', 'AWS::Kinesis'); testAwsSdkServiceNormalization('S3', 'AWS::S3'); testAwsSdkServiceNormalization('SQS', 'AWS::SQS'); + testAwsSdkServiceNormalization('SNS', 'AWS::SNS'); + testAwsSdkServiceNormalization('Lambda', 'AWS::Lambda'); + testAwsSdkServiceNormalization('SecretsManager', 'AWS::SecretsManager'); + testAwsSdkServiceNormalization('SFN', 'AWS::StepFunctions'); testAwsSdkServiceNormalization('Bedrock', 'AWS::Bedrock'); testAwsSdkServiceNormalization('BedrockAgent', 'AWS::Bedrock'); testAwsSdkServiceNormalization('BedrockAgentRuntime', 'AWS::Bedrock'); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts index b1516c6..f642e09 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts @@ -134,7 +134,7 @@ describe('BedrockAgent', () => { expect(getDataSourceSpans.length).toBe(1); const getDataSourceSpan = getDataSourceSpans[0]; expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); - expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBe(dummyKnowledgeBaseId); expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBe(dummyDataSourceId); expect(getDataSourceSpan.kind).toBe(SpanKind.CLIENT); }); @@ -236,9 +236,11 @@ describe('Bedrock', () => { describe('GetGuardrail', () => { it('adds guardrailId to span', async () => { const dummyGuardrailIdentifier: string = 'ABCDEFGH'; + const guardrailArn: string = 'arn:aws:bedrock:us-east-1:123456789012:guardrail/abc123'; nock(`https://bedrock.${region}.amazonaws.com`).get(`/guardrails/${dummyGuardrailIdentifier}`).reply(200, { guardrailId: dummyGuardrailIdentifier, + guardrailArn: guardrailArn, }); await bedrock @@ -258,6 +260,7 @@ describe('Bedrock', () => { expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]).toBe(dummyGuardrailIdentifier); + expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN]).toBe(guardrailArn); expect(getGuardrailSpan.kind).toBe(SpanKind.CLIENT); }); }); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/secretsmanager.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/secretsmanager.test.ts new file mode 100644 index 0000000..7e84777 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/secretsmanager.test.ts @@ -0,0 +1,100 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { getTestSpans, registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils'; +import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'; +import { applyInstrumentationPatches } from './../../../../src/patches/instrumentation-patch'; + +const instrumentations: AwsInstrumentation[] = [new AwsInstrumentation()]; +applyInstrumentationPatches(instrumentations); +registerInstrumentationTesting(instrumentations[0]); + +import { SecretsManager } from '@aws-sdk/client-secrets-manager'; +import * as nock from 'nock'; + +import { SpanKind } from '@opentelemetry/api'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { expect } from 'expect'; +import { AWS_ATTRIBUTE_KEYS } from '../../../../src/aws-attribute-keys'; + +const region = 'us-east-1'; + +describe('SecretsManager', () => { + let secretsManager: SecretsManager; + beforeEach(() => { + secretsManager = new SecretsManager({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('DescribeSecret', () => { + const testParams = [ + 'testId', + 'badarn:aws:secretsmanager:us-weast-1:123456789123:secret:testId123456', + 'arn:aws:secretsmanager:us-east-1:123456789123:secret:testId123456', + ]; + + testParams.forEach(secretId => { + it('should generate secret arn attribute only if secretId is an valid ARN', async () => { + nock(`https://secretsmanager.${region}.amazonaws.com/`).post('/').reply(200, 'null'); + + await secretsManager + .describeSecret({ + SecretId: secretId, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getDescribeSecretSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'SecretsManager.DescribeSecret'; + }); + + expect(getDescribeSecretSpans.length).toBe(1); + const describeSecretSpan = getDescribeSecretSpans[0]; + + if (secretId.startsWith('arn:aws:secretsmanager:')) { + expect(describeSecretSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]).toBe(secretId); + } else { + expect(describeSecretSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]).toBeUndefined(); + } + + expect(describeSecretSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + }); + + describe('GetSecretValue', () => { + it('secret arn attribute should be populated from the response', async () => { + const secretIdArn = 'arn:aws:secretsmanager:us-east-1:123456789123:secret:testId123456'; + + nock(`https://secretsmanager.${region}.amazonaws.com/`).post('/').reply(200, { + ARN: secretIdArn, + Name: 'testId', + }); + + await secretsManager + .getSecretValue({ + SecretId: 'testSecret', + }) + .catch((err: any) => { + console.log(err); + }); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getSecretValueSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'SecretsManager.GetSecretValue'; + }); + + expect(getSecretValueSpans.length).toBe(1); + + const secretValueSpan = getSecretValueSpans[0]; + + expect(secretValueSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]).toBe(secretIdArn); + expect(secretValueSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/step-functions.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/step-functions.test.ts new file mode 100644 index 0000000..8d9a5a5 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/step-functions.test.ts @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { getTestSpans, registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils'; +import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'; +import { applyInstrumentationPatches } from './../../../../src/patches/instrumentation-patch'; + +const instrumentations: AwsInstrumentation[] = [new AwsInstrumentation()]; +applyInstrumentationPatches(instrumentations); +registerInstrumentationTesting(instrumentations[0]); + +import { SFN } from '@aws-sdk/client-sfn'; +import * as nock from 'nock'; + +import { SpanKind } from '@opentelemetry/api'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { expect } from 'expect'; +import { AWS_ATTRIBUTE_KEYS } from '../../../../src/aws-attribute-keys'; + +const region = 'us-east-1'; + +describe('SFN', () => { + let sfn: SFN; + beforeEach(() => { + sfn = new SFN({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('DescribeStateMachine', () => { + it('span has stateMachineArn in its attributes', async () => { + const stateMachineArn: string = 'arn:aws:states:us-east-1:123456789123:stateMachine:testStateMachine'; + + nock(`https://states.${region}.amazonaws.com/`).post('/').reply(200, 'null'); + + await sfn + .describeStateMachine({ + stateMachineArn: stateMachineArn, + }) + .catch((err: any) => { + console.log(err); + }); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getStateMachineAttributeSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'SFN.DescribeStateMachine'; + }); + + expect(getStateMachineAttributeSpans.length).toBe(1); + + const stateMachineAttributeSpan = getStateMachineAttributeSpans[0]; + + expect(AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN in stateMachineAttributeSpan.attributes).toBe(true); + expect(stateMachineAttributeSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN]).toBe( + stateMachineArn + ); + expect(stateMachineAttributeSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + + describe('DescribeActivity', () => { + it('span has activityArn in its attributes', async () => { + const activityArn: string = 'arn:aws:states:us-east-1:123456789123:activity:testActivity'; + + nock(`https://states.${region}.amazonaws.com/`).post('/').reply(200, 'null'); + + await sfn + .describeActivity({ + activityArn: activityArn, + }) + .catch((err: any) => { + console.log(err); + }); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getActivityAttributeSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'SFN.DescribeActivity'; + }); + + expect(getActivityAttributeSpans.length).toBe(1); + + const activityAttributeSpan = getActivityAttributeSpans[0]; + + expect(AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN in activityAttributeSpan.attributes).toBe(true); + expect(activityAttributeSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN]).toBe(activityArn); + expect(activityAttributeSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts index 9c11b06..6a731f2 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts @@ -32,10 +32,16 @@ import { AwsSdkInstrumentationExtended } from '../../src/patches/extended-instru const _STREAM_NAME: string = 'streamName'; const _BUCKET_NAME: string = 'bucketName'; const _QUEUE_NAME: string = 'queueName'; +const _ACTIVITY_ARN: string = 'arn:aws:states:us-east-1:123456789123:activity:testActivity'; +const _STATE_MACHINE_ARN: string = 'arn:aws:states:us-east-1:123456789123:stateMachine:testStateMachine'; +const _SECRETS_ARN: string = 'arn:aws:secretsmanager:us-east-1:123456789123:secret:testId123456'; +const _UUID: string = 'random-uuid'; +const _TOPIC_ARN: string = 'arn:aws:sns:us-east-1:123456789012:mystack-mytopic-NZJ5JSMVGFIE'; const _QUEUE_URL: string = 'https://sqs.us-east-1.amazonaws.com/123412341234/queueName'; const _BEDROCK_AGENT_ID: string = 'agentId'; const _BEDROCK_DATASOURCE_ID: string = 'DataSourceId'; const _BEDROCK_GUARDRAIL_ID: string = 'GuardrailId'; +const _BEDROCK_GUARDRAIL_ARN: string = 'arn:aws:bedrock:us-east-1:123456789012:guardrail/abc123'; const _BEDROCK_KNOWLEDGEBASE_ID: string = 'KnowledgeBaseId'; const _GEN_AI_SYSTEM: string = 'aws.bedrock'; const _GEN_AI_REQUEST_MODEL: string = 'genAiReuqestModelId'; @@ -62,11 +68,18 @@ describe('InstrumentationPatchTest', () => { // Not from patching expect(services.has('SQS')).toBeTruthy(); expect(services.has('SNS')).toBeTruthy(); - expect(services.has('DynamoDB')).toBeTruthy(); expect(services.has('Lambda')).toBeTruthy(); + + expect(services.has('DynamoDB')).toBeTruthy(); // From patching but shouldn't be applied + expect(services.get('SecretsManager')).toBeFalsy(); + expect(services.get('SFN')).toBeFalsy(); expect(services.has('S3')).toBeFalsy(); expect(services.has('Kinesis')).toBeFalsy(); + expect(services.get('SNS')._requestPreSpanHook).toBeFalsy(); + expect(services.get('SNS').requestPreSpanHook).toBeTruthy(); + expect(services.get('Lambda')._requestPreSpanHook).toBeFalsy(); + expect(services.get('Lambda').requestPreSpanHook).toBeTruthy(); expect(services.get('SQS')._requestPreSpanHook).toBeFalsy(); expect(services.get('SQS').requestPreSpanHook).toBeTruthy(); expect(services.has('Bedrock')).toBeFalsy(); @@ -87,9 +100,16 @@ describe('InstrumentationPatchTest', () => { expect(services.has('SNS')).toBeTruthy(); expect(services.has('DynamoDB')).toBeTruthy(); expect(services.has('Lambda')).toBeTruthy(); + // From patching + expect(services.has('SecretsManager')).toBeTruthy(); + expect(services.has('SFN')).toBeTruthy(); expect(services.has('S3')).toBeTruthy(); expect(services.has('Kinesis')).toBeTruthy(); + expect(services.get('SNS')._requestPreSpanHook).toBeTruthy(); + expect(services.get('SNS').requestPreSpanHook).toBeTruthy(); + expect(services.get('Lambda')._requestPreSpanHook).toBeTruthy(); + expect(services.get('Lambda').requestPreSpanHook).toBeTruthy(); expect(services.get('SQS')._requestPreSpanHook).toBeTruthy(); expect(services.get('SQS').requestPreSpanHook).toBeTruthy(); expect(services.has('Bedrock')).toBeTruthy(); @@ -129,6 +149,36 @@ describe('InstrumentationPatchTest', () => { expect(sqsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]).toBeUndefined(); }); + it('SNS without patching', () => { + const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); + expect(() => doExtractSNSAttributes(services)).not.toThrow(); + + const snsAttributes = doExtractSNSAttributes(services); + expect(snsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN]).toBeUndefined(); + }); + + it('Lambda without patching', () => { + const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); + expect(() => doExtractLambdaAttributes(services)).not.toThrow(); + + const lambdaAttributes: Attributes = doExtractLambdaAttributes(services); + expect(lambdaAttributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID]).toBeUndefined(); + }); + + it('SFN without patching', () => { + const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); + expect(() => doExtractSFNAttributes(services)).toThrow(); + }); + + it('SecretsManager without patching', () => { + const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); + expect(() => doExtractSecretsManagerAttributes(services)).toThrow(); + }); + it('Bedrock without patching', () => { const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); @@ -149,6 +199,14 @@ describe('InstrumentationPatchTest', () => { expect(kinesisAttributes[AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME]).toEqual(_STREAM_NAME); }); + it('SNS with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + + const snsAttributes = doExtractSNSAttributes(services); + expect(snsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN]).toBe(_TOPIC_ARN); + }); + it('SQS with patching', () => { const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); @@ -165,6 +223,33 @@ describe('InstrumentationPatchTest', () => { expect(sqsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]).toEqual(_QUEUE_NAME); }); + it('Lambda with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const requestLambdaAttributes: Attributes = doExtractLambdaAttributes(services); + expect(requestLambdaAttributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID]).toEqual(_UUID); + }); + + it('SFN with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const requestSFNAttributes: Attributes = doExtractSFNAttributes(services); + expect(requestSFNAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN]).toEqual(_STATE_MACHINE_ARN); + expect(requestSFNAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN]).toEqual(_ACTIVITY_ARN); + }); + + it('SecretsManager with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const requestSecretsManagerAttributes: Attributes = doExtractSecretsManagerAttributes(services); + + expect(requestSecretsManagerAttributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]).toBe(_SECRETS_ARN); + + const responseHookSecretsManagerAttributes = doResponseHookSecretsManager(services); + + expect(responseHookSecretsManagerAttributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]).toBe(_SECRETS_ARN); + }); + it('Bedrock with patching', () => { const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); @@ -172,8 +257,11 @@ describe('InstrumentationPatchTest', () => { // Expect no-op from attribute extraction in Bedrock expect(Object.entries(bedrockAttributes).length).toEqual(0); const bedrockAttributesAfterResponse: Attributes = doResponseHookBedrock(services, 'Bedrock'); - expect(Object.entries(bedrockAttributesAfterResponse).length).toBe(1); + expect(Object.entries(bedrockAttributesAfterResponse).length).toBe(2); expect(bedrockAttributesAfterResponse[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]).toEqual(_BEDROCK_GUARDRAIL_ID); + expect(bedrockAttributesAfterResponse[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN]).toEqual( + _BEDROCK_GUARDRAIL_ARN + ); }); it('Bedrock Agent with patching', () => { @@ -208,18 +296,25 @@ describe('InstrumentationPatchTest', () => { ListDataSources: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, UpdateAgentKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, DeleteDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, - GetDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, + GetDataSource: { + 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID, + 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID, + }, UpdateDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, }; for (const [operation, attribute_tuple] of Object.entries(operation_to_expected_attribute)) { const bedrockAttributes: Attributes = doExtractBedrockAttributes(services, 'BedrockAgent', operation); - const [attribute_key, attribute_value] = Object.entries(attribute_tuple)[0]; - expect(Object.entries(bedrockAttributes).length).toBe(1); - expect(bedrockAttributes[attribute_key]).toEqual(attribute_value); + + for (const [attribute_key, attribute_value] of Object.entries(attribute_tuple)) { + expect(bedrockAttributes[attribute_key]).toEqual(attribute_value); + } + const bedrockAgentSuccessAttributes: Attributes = doResponseHookBedrock(services, 'BedrockAgent', operation); - expect(Object.entries(bedrockAgentSuccessAttributes).length).toBe(1); - expect(bedrockAgentSuccessAttributes[attribute_key]).toEqual(attribute_value); + + for (const [attribute_key, attribute_value] of Object.entries(attribute_tuple)) { + expect(bedrockAgentSuccessAttributes[attribute_key]).toEqual(attribute_value); + } } }); @@ -315,6 +410,55 @@ describe('InstrumentationPatchTest', () => { return doExtractAttributes(services, serviceName, params); } + function doExtractSNSAttributes(services: Map): Attributes { + const serviceName: string = 'SNS'; + const params: NormalizedRequest = { + serviceName: serviceName, + commandName: 'mockCommandName', + commandInput: { + TopicArn: _TOPIC_ARN, + }, + }; + return doExtractAttributes(services, serviceName, params); + } + + function doExtractLambdaAttributes(services: Map): Attributes { + const serviceName: string = 'Lambda'; + const params: NormalizedRequest = { + serviceName: serviceName, + commandName: 'mockCommandName', + commandInput: { + UUID: _UUID, + }, + }; + return doExtractAttributes(services, serviceName, params); + } + + function doExtractSFNAttributes(services: Map): Attributes { + const serviceName: string = 'SFN'; + const params: NormalizedRequest = { + serviceName: serviceName, + commandName: 'mockCommandName', + commandInput: { + stateMachineArn: _STATE_MACHINE_ARN, + activityArn: _ACTIVITY_ARN, + }, + }; + return doExtractAttributes(services, serviceName, params); + } + + function doExtractSecretsManagerAttributes(services: Map): Attributes { + const serviceName: string = 'SecretsManager'; + const params: NormalizedRequest = { + serviceName: serviceName, + commandName: 'mockCommandName', + commandInput: { + SecretId: _SECRETS_ARN, + }, + }; + return doExtractAttributes(services, serviceName, params); + } + function doExtractBedrockAttributes( services: Map, serviceName: string, @@ -347,6 +491,22 @@ describe('InstrumentationPatchTest', () => { return requestMetadata.spanAttributes || {}; } + function doResponseHookSecretsManager(services: Map): Attributes { + const results: Partial = { + data: { + ARN: _SECRETS_ARN, + }, + + request: { + commandInput: {}, + commandName: 'dummy_operation', + serviceName: 'SecretsManager', + }, + }; + + return doResponseHook(services, 'SecretsManager', results as NormalizedResponse); + } + function doResponseHookBedrock( services: Map, serviceName: string, @@ -358,6 +518,7 @@ describe('InstrumentationPatchTest', () => { dataSourceId: _BEDROCK_DATASOURCE_ID, knowledgeBaseId: _BEDROCK_KNOWLEDGEBASE_ID, guardrailId: _BEDROCK_GUARDRAIL_ID, + guardrailArn: _BEDROCK_GUARDRAIL_ARN, modelId: _GEN_AI_REQUEST_MODEL, }, request: { diff --git a/contract-tests/images/applications/mysql2/package.json b/contract-tests/images/applications/mysql2/package.json index 0c1fe64..5a18ef9 100644 --- a/contract-tests/images/applications/mysql2/package.json +++ b/contract-tests/images/applications/mysql2/package.json @@ -12,4 +12,4 @@ "dependencies": { "mysql2": "3.11.4" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 97ce276..7d8f801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,11 @@ "@aws-sdk/client-bedrock-agent-runtime": "3.632.0", "@aws-sdk/client-bedrock-runtime": "3.632.0", "@aws-sdk/client-kinesis": "3.632.0", + "@aws-sdk/client-lambda": "^3.632.0", "@aws-sdk/client-s3": "3.632.0", + "@aws-sdk/client-secrets-manager": "^3.632.0", + "@aws-sdk/client-sfn": "^3.632.0", + "@aws-sdk/client-sns": "^3.632.0", "@opentelemetry/contrib-test-utils": "0.41.0", "@types/mocha": "7.0.2", "@types/node": "18.6.5", @@ -613,48 +617,782 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.699.0.tgz", + "integrity": "sha512-K9TGvQB8hkjwNhfWSfYllUpttqxTcd78ShSRCIhlcwzzsmQphET10xEb0Tm1k8sqriSQ+CiVOFSkX78gqoHzBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.699.0", + "@aws-sdk/client-sts": "3.699.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/eventstream-serde-browser": "^3.0.13", + "@smithy/eventstream-serde-config-resolver": "^3.0.10", + "@smithy/eventstream-serde-node": "^3.0.12", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-stream": "^3.3.1", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.9", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.696.0.tgz", + "integrity": "sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.699.0.tgz", + "integrity": "sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.699.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/client-sts": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.699.0.tgz", + "integrity": "sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.699.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-node": "3.699.0", + "@aws-sdk/middleware-host-header": "3.696.0", + "@aws-sdk/middleware-logger": "3.696.0", + "@aws-sdk/middleware-recursion-detection": "3.696.0", + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/region-config-resolver": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@aws-sdk/util-user-agent-browser": "3.696.0", + "@aws-sdk/util-user-agent-node": "3.696.0", + "@smithy/config-resolver": "^3.0.12", + "@smithy/core": "^2.5.3", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/hash-node": "^3.0.10", + "@smithy/invalid-dependency": "^3.0.10", + "@smithy/middleware-content-length": "^3.0.12", + "@smithy/middleware-endpoint": "^3.2.3", + "@smithy/middleware-retry": "^3.0.27", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.27", + "@smithy/util-defaults-mode-node": "^3.0.27", + "@smithy/util-endpoints": "^2.1.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/core": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.696.0.tgz", + "integrity": "sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/core": "^2.5.3", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.7", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.696.0.tgz", + "integrity": "sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.696.0.tgz", + "integrity": "sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.7", + "@smithy/smithy-client": "^3.4.4", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.699.0.tgz", + "integrity": "sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/credential-provider-env": "3.696.0", + "@aws-sdk/credential-provider-http": "3.696.0", + "@aws-sdk/credential-provider-process": "3.696.0", + "@aws-sdk/credential-provider-sso": "3.699.0", + "@aws-sdk/credential-provider-web-identity": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.699.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.699.0.tgz", + "integrity": "sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.696.0", + "@aws-sdk/credential-provider-http": "3.696.0", + "@aws-sdk/credential-provider-ini": "3.699.0", + "@aws-sdk/credential-provider-process": "3.696.0", + "@aws-sdk/credential-provider-sso": "3.699.0", + "@aws-sdk/credential-provider-web-identity": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.696.0.tgz", + "integrity": "sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.699.0.tgz", + "integrity": "sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.696.0", + "@aws-sdk/core": "3.696.0", + "@aws-sdk/token-providers": "3.699.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.696.0.tgz", + "integrity": "sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.696.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.696.0.tgz", + "integrity": "sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-logger": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.696.0.tgz", + "integrity": "sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.696.0.tgz", + "integrity": "sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.696.0.tgz", + "integrity": "sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@aws-sdk/util-endpoints": "3.696.0", + "@smithy/core": "^2.5.3", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.696.0.tgz", + "integrity": "sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/token-providers": { + "version": "3.699.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.699.0.tgz", + "integrity": "sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.699.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.696.0.tgz", + "integrity": "sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-endpoints": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.696.0.tgz", + "integrity": "sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/types": "^3.7.1", + "@smithy/util-endpoints": "^2.1.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.696.0.tgz", + "integrity": "sha512-Z5rVNDdmPOe6ELoM5AhF/ja5tSjbe6ctSctDPb0JdDf4dT0v2MfwhJKzXju2RzX8Es/77Glh7MlaXLE0kCB9+Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.696.0", + "@smithy/types": "^3.7.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.696.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.696.0.tgz", + "integrity": "sha512-KhKqcfyXIB0SCCt+qsu4eJjsfiOrNzK5dCV7RAW2YIpp+msxGUUX0NdRE9rkzjiv+3EMktgJm3eEIS+yxtlVdQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.696.0", + "@aws-sdk/types": "3.696.0", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", + "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, "node_modules/@aws-sdk/client-s3": { "version": "3.632.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.632.0.tgz", - "integrity": "sha512-GlhLo/t/E9w3EG3ZhFsSeSM3op2lfhc1LrLMXHFsmH1xIPkjIoxV+aHYIenuF7b/MGgMQDOjf07QeQNXfAr1gg==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.632.0.tgz", + "integrity": "sha512-GlhLo/t/E9w3EG3ZhFsSeSM3op2lfhc1LrLMXHFsmH1xIPkjIoxV+aHYIenuF7b/MGgMQDOjf07QeQNXfAr1gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.629.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.629.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.632.0.tgz", + "integrity": "sha512-WsQhPHHK1yPfALcP1B7nBSGDzky6vFTUEXnUdfzb5Xy2cT+JTBTS6ChtQGqqOuGHDP/3t/9soqZ+L6rUCYBb/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sfn": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sfn/-/client-sfn-3.632.0.tgz", + "integrity": "sha512-LPaMqALuArGX1Y5N3tHX4lNlZabsqgfGJZjwyLTHawqiGNDMlMqOUg4Dl0e2Ies8yvMjs//YXtMWz26x24cGkQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sns": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.632.0.tgz", + "integrity": "sha512-I8S0tFx26LL59/PiL4Reqna+LW7oIbRB+jygl4vrf4XIvpX5o4tmZmOmR0caYA+Cks3AomCi4cp+7WuwUpuATQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.632.0", "@aws-sdk/client-sts": "3.632.0", "@aws-sdk/core": "3.629.0", "@aws-sdk/credential-provider-node": "3.632.0", - "@aws-sdk/middleware-bucket-endpoint": "3.620.0", - "@aws-sdk/middleware-expect-continue": "3.620.0", - "@aws-sdk/middleware-flexible-checksums": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-location-constraint": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-sdk-s3": "3.629.0", - "@aws-sdk/middleware-ssec": "3.609.0", "@aws-sdk/middleware-user-agent": "3.632.0", "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/signature-v4-multi-region": "3.629.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.632.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", - "@aws-sdk/xml-builder": "3.609.0", "@smithy/config-resolver": "^3.0.5", "@smithy/core": "^2.3.2", - "@smithy/eventstream-serde-browser": "^3.0.6", - "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.5", "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-blob-browser": "^3.1.2", "@smithy/hash-node": "^3.0.3", - "@smithy/hash-stream-node": "^3.1.2", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/md5-js": "^3.0.3", "@smithy/middleware-content-length": "^3.0.5", "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.14", @@ -674,9 +1412,7 @@ "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.1.3", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" }, "engines": { @@ -5544,12 +6280,13 @@ "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", + "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5578,15 +6315,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.8.tgz", - "integrity": "sha512-Tv1obAC18XOd2OnDAjSWmmthzx6Pdeh63FbLin8MlPiuJ2ATpKkq0NcNOJFr0dO+JmZXnwu8FQxKJ3TKJ3Hulw==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", + "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -5594,19 +6332,18 @@ } }, "node_modules/@smithy/core": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.3.tgz", - "integrity": "sha512-4LTusLqFMRVQUfC3RNuTg6IzYTeJNpydRdTKq7J5wdEyIRQSu3rGIa3s80mgG2hhe6WOZl9IqTSo1pgbn6EHhA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.4.tgz", + "integrity": "sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-stream": "^3.3.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -5615,15 +6352,16 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.3.tgz", - "integrity": "sha512-VoxMzSzdvkkjMJNE38yQgx4CfnmT+Z+5EUXkg4x7yag93eQkVQgZvN3XBSHC/ylfBbLbAtdu7flTCChX9I+mVg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", + "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -5631,25 +6369,27 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.5.tgz", - "integrity": "sha512-6pu+PT2r+5ZnWEV3vLV1DzyrpJ0TmehQlniIDCSpZg6+Ji2SfOI38EqUyQ+O8lotVElCrfVc9chKtSMe9cmCZQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.9.tgz", + "integrity": "sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "@smithy/util-hex-encoding": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.9.tgz", - "integrity": "sha512-PiQLo6OQmZAotJweIcObL1H44gkvuJACKMNqpBBe5Rf2Ax1DOcGi/28+feZI7yTe1ERHlQQaGnm8sSkyDUgsMg==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.13.tgz", + "integrity": "sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.8", - "@smithy/types": "^3.4.2", + "@smithy/eventstream-serde-universal": "^3.0.12", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5657,12 +6397,13 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.6.tgz", - "integrity": "sha512-iew15It+c7WfnVowWkt2a7cdPp533LFJnpjDQgfZQcxv2QiOcyEcea31mnrk5PVbgo0nNH3VbYGq7myw2q/F6A==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.10.tgz", + "integrity": "sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5670,13 +6411,14 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.8.tgz", - "integrity": "sha512-6m+wI+fT0na+6oao6UqALVA38fsScCpoG5UO/A8ZSyGLnPM2i4MS1cFUhpuALgvLMxfYoTCh7qSeJa0aG4IWpQ==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.12.tgz", + "integrity": "sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.8", - "@smithy/types": "^3.4.2", + "@smithy/eventstream-serde-universal": "^3.0.12", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5684,13 +6426,14 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.8.tgz", - "integrity": "sha512-09tqzIQ6e+7jLqGvRji1yJoDbL/zob0OFhq75edgStWErGLf16+yI5hRc/o9/YAybOhUZs/swpW2SPn892G5Gg==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.12.tgz", + "integrity": "sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^3.1.5", - "@smithy/types": "^3.4.2", + "@smithy/eventstream-codec": "^3.1.9", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5724,12 +6467,13 @@ } }, "node_modules/@smithy/hash-node": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.6.tgz", - "integrity": "sha512-c/FHEdKK/7DU2z6ZE91L36ahyXWayR3B+FzELjnYq7wH5YqIseM24V+pWCS9kFn1Ln8OFGTf+pyYPiHZuX0s/Q==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", + "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -5754,12 +6498,13 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.6.tgz", - "integrity": "sha512-czM7Ioq3s8pIXht7oD+vmgy4Wfb4XavU/k/irO8NdXFFOx7YAlsCCcKOh/lJD1mJSYQqiR7NmpZ9JviryD/7AQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", + "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -5789,13 +6534,14 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.8.tgz", - "integrity": "sha512-VuyszlSO49WKh3H9/kIO2kf07VUwGV80QRiaDxUfP8P8UKlokz381ETJvwLhwuypBYhLymCYyNhB3fLAGBX2og==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", + "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5803,17 +6549,19 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.3.tgz", - "integrity": "sha512-KeM/OrK8MVFUsoJsmCN0MZMVPjKKLudn13xpgwIMpGTYpA8QZB2Xq5tJ+RE6iu3A6NhOI4VajDTwBsm8pwwrhg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.4.tgz", + "integrity": "sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-middleware": "^3.0.6", + "@smithy/core": "^2.5.4", + "@smithy/middleware-serde": "^3.0.10", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", + "@smithy/url-parser": "^3.0.10", + "@smithy/util-middleware": "^3.0.10", "tslib": "^2.6.2" }, "engines": { @@ -5821,18 +6569,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.18.tgz", - "integrity": "sha512-YU1o/vYob6vlqZdd97MN8cSXRToknLXhFBL3r+c9CZcnxkO/rgNZ++CfgX2vsmnEKvlqdi26+SRtSzlVp5z6Mg==", + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.28.tgz", + "integrity": "sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/service-error-classification": "^3.0.6", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/protocol-http": "^4.1.7", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", + "@smithy/util-middleware": "^3.0.10", + "@smithy/util-retry": "^3.0.10", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -5841,12 +6590,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.6.tgz", - "integrity": "sha512-KKTUSl1MzOM0MAjGbudeaVNtIDo+PpekTBkCNwvfZlKndodrnvRo+00USatiyLOc0ujjO9UydMRu3O9dYML7ag==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", + "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5854,12 +6604,13 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.6.tgz", - "integrity": "sha512-2c0eSYhTQ8xQqHMcRxLMpadFbTXg6Zla5l0mwNftFCZMQmuhI7EbAJMx6R5eqfuV3YbJ3QGyS3d5uSmrHV8Khg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", + "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5867,14 +6618,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.7.tgz", - "integrity": "sha512-g3mfnC3Oo8pOI0dYuPXLtdW1WGVb3bR2tkV21GNkm0ZvQjLTtamXAwCWt/FCb0HGvKt3gHHmF1XerG0ICfalOg==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", + "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/property-provider": "^3.1.10", + "@smithy/shared-ini-file-loader": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5882,15 +6634,16 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.2.tgz", - "integrity": "sha512-42Cy4/oT2O+00aiG1iQ7Kd7rE6q8j7vI0gFfnMlUiATvyo8vefJkhb7O10qZY0jAqo5WZdUzfl9IV6wQ3iMBCg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", + "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/abort-controller": "^3.1.8", + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5898,12 +6651,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.6.tgz", - "integrity": "sha512-NK3y/T7Q/Bw+Z8vsVs9MYIQ5v7gOX7clyrXcwhhIBQhbPgRl6JDrZbusO9qWDhcEus75Tg+VCxtIRfo3H76fpw==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", + "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5911,12 +6665,13 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", + "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5924,12 +6679,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", + "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -5938,12 +6694,13 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.6.tgz", - "integrity": "sha512-UJKw4LlEkytzz2Wq+uIdHf6qOtFfee/o7ruH0jF5I6UAuU+19r9QV7nU3P/uI0l6+oElRHmG/5cBBcGJrD7Ozg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", + "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5951,24 +6708,26 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.6.tgz", - "integrity": "sha512-53SpchU3+DUZrN7J6sBx9tBiCVGzsib2e4sc512Q7K9fpC5zkJKs6Z9s+qbMxSYrkEkle6hnMtrts7XNkMJJMg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", + "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2" + "@smithy/types": "^3.7.1" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.7.tgz", - "integrity": "sha512-IA4K2qTJYXkF5OfVN4vsY1hfnUZjaslEE8Fsr/gGFza4TAC2A9NfnZuSY2srQIbt9bwtjHiAayrRVgKse4Q7fA==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", + "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -5976,16 +6735,17 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.3.tgz", - "integrity": "sha512-YD2KYSCEEeFHcWZ1E3mLdAaHl8T/TANh6XwmocQ6nPcTdBfh4N5fusgnblnWDlnlU1/cUqEq3PiGi22GmT2Lkg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", + "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.10", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -5995,16 +6755,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.3.2.tgz", - "integrity": "sha512-RKDfhF2MTwXl7jan5d7QfS9eCC6XJbO3H+EZAvLQN8A5in4ib2Ml4zoeLo57w9QrqFekBPcsoC2hW3Ekw4vQ9Q==", + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.5.tgz", + "integrity": "sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", + "@smithy/core": "^2.5.4", + "@smithy/middleware-endpoint": "^3.2.4", + "@smithy/middleware-stack": "^3.0.10", + "@smithy/protocol-http": "^4.1.7", + "@smithy/types": "^3.7.1", + "@smithy/util-stream": "^3.3.1", "tslib": "^2.6.2" }, "engines": { @@ -6012,10 +6774,11 @@ } }, "node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", + "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -6024,13 +6787,14 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.6.tgz", - "integrity": "sha512-47Op/NU8Opt49KyGpHtVdnmmJMsp2hEwBdyjuFB9M2V5QVOwA7pBhhxKN5z6ztKGrMw76gd8MlbPuzzvaAncuQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", + "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/querystring-parser": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" } }, @@ -6100,14 +6864,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.18.tgz", - "integrity": "sha512-/eveCzU6Z6Yw8dlYQLA4rcK30XY0E4L3lD3QFHm59mzDaWYelrXE1rlynuT3J6qxv+5yNy3a1JuzhG5hk5hcmw==", + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.28.tgz", + "integrity": "sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.6", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -6116,17 +6881,18 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.18.tgz", - "integrity": "sha512-9cfzRjArtOFPlTYRREJk00suUxVXTgbrzVncOyMRTUeMKnecG/YentLF3cORa+R6mUOMSrMSnT18jos1PKqK6Q==", + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.28.tgz", + "integrity": "sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.8", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", + "@smithy/config-resolver": "^3.0.12", + "@smithy/credential-provider-imds": "^3.2.7", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/property-provider": "^3.1.10", + "@smithy/smithy-client": "^3.4.5", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -6134,13 +6900,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.2.tgz", - "integrity": "sha512-FEISzffb4H8DLzGq1g4MuDpcv6CIG15fXoQzDH9SjpRJv6h7J++1STFWWinilG0tQh9H1v2UKWG19Jjr2B16zQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", + "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/node-config-provider": "^3.1.11", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -6161,12 +6928,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.6.tgz", - "integrity": "sha512-BxbX4aBhI1O9p87/xM+zWy0GzT3CEVcXFPBRDoHAM+pV0eSW156pR+PSYEz0DQHDMYDsYAflC2bQNz2uaDBUZQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", + "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -6174,13 +6942,14 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.6.tgz", - "integrity": "sha512-BRZiuF7IwDntAbevqMco67an0Sr9oLQJqqRCsSPZZHYRnehS0LHDAkJk/pSmI7Z8c/1Vet294H7fY2fWUgB+Rg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", + "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/service-error-classification": "^3.0.10", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { @@ -6188,14 +6957,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.6.tgz", - "integrity": "sha512-lQEUfTx1ht5CRdvIjdAN/gUL6vQt2wSARGGLaBHNe+iJSkRHlWzY+DOn0mFTmTgyU3jcI5n9DkT5gTzYuSOo6A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", + "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/types": "^3.4.2", + "@smithy/fetch-http-handler": "^4.1.1", + "@smithy/node-http-handler": "^3.3.1", + "@smithy/types": "^3.7.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -6206,6 +6976,20 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", + "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.7", + "@smithy/querystring-builder": "^3.0.10", + "@smithy/types": "^3.7.1", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", @@ -6234,14 +7018,14 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.5.tgz", - "integrity": "sha512-jYOSvM3H6sZe3CHjzD2VQNCjWBJs+4DbtwBMvUp9y5EnnwNa7NQxTeYeQw0CKCAdGGZ3QvVkyJmvbvs5M/B10A==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", + "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/types": "^3.4.2", + "@smithy/abort-controller": "^3.1.8", + "@smithy/types": "^3.7.1", "tslib": "^2.6.2" }, "engines": { From a35f89ca1227997283c621ca0c38a4bb63a80723 Mon Sep 17 00:00:00 2001 From: Steve Liu Date: Mon, 16 Dec 2024 15:17:41 -0800 Subject: [PATCH 4/7] [JS Feature Parity] Contract tests for new AWS resources and CFN primary id (#126) *Description of changes:* Added/updated contract tests to verify new aws resources and new CFN primary id attribute. image By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: Min Xia --- .../images/applications/aws-sdk/package.json | 6 + .../images/applications/aws-sdk/server.js | 403 +++++++++++++++++- .../tests/test/amazon/aws-sdk/aws_sdk_test.py | 368 ++++++++++++++-- .../test/amazon/base/contract_test_base.py | 5 +- .../utils/application_signals_constants.py | 1 + 5 files changed, 743 insertions(+), 40 deletions(-) diff --git a/contract-tests/images/applications/aws-sdk/package.json b/contract-tests/images/applications/aws-sdk/package.json index cef37d8..8596d51 100644 --- a/contract-tests/images/applications/aws-sdk/package.json +++ b/contract-tests/images/applications/aws-sdk/package.json @@ -15,10 +15,16 @@ "@aws-sdk/client-bedrock-agent-runtime": "^3.676.0", "@aws-sdk/client-bedrock-runtime": "^3.675.0", "@aws-sdk/client-dynamodb": "^3.658.1", + "@aws-sdk/client-iam": "^3.696.0", "@aws-sdk/client-kinesis": "^3.658.1", + "@aws-sdk/client-lambda": "^3.698.0", "@aws-sdk/client-s3": "^3.658.1", + "@aws-sdk/client-secrets-manager": "^3.696.0", + "@aws-sdk/client-sfn": "^3.696.0", + "@aws-sdk/client-sns": "^3.696.0", "@aws-sdk/client-sqs": "^3.658.1", "@smithy/node-http-handler": "^3.2.3", + "jszip": "^3.10.1", "node-fetch": "^2.7.0" } } diff --git a/contract-tests/images/applications/aws-sdk/server.js b/contract-tests/images/applications/aws-sdk/server.js index 6fd65c7..7352bfa 100644 --- a/contract-tests/images/applications/aws-sdk/server.js +++ b/contract-tests/images/applications/aws-sdk/server.js @@ -5,6 +5,8 @@ const fs = require('fs'); const os = require('os'); const ospath = require('path'); const { NodeHttpHandler } =require('@smithy/node-http-handler'); +const fetch = require('node-fetch'); +const JSZip = require('jszip'); const { S3Client, CreateBucketCommand, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); const { DynamoDBClient, CreateTableCommand, PutItemCommand } = require('@aws-sdk/client-dynamodb'); @@ -14,6 +16,11 @@ const { BedrockClient, GetGuardrailCommand } = require('@aws-sdk/client-bedrock' const { BedrockAgentClient, GetKnowledgeBaseCommand, GetDataSourceCommand, GetAgentCommand } = require('@aws-sdk/client-bedrock-agent'); const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime'); const { BedrockAgentRuntimeClient, InvokeAgentCommand, RetrieveCommand } = require('@aws-sdk/client-bedrock-agent-runtime'); +const { SNSClient, CreateTopicCommand, GetTopicAttributesCommand } = require('@aws-sdk/client-sns'); +const { SecretsManagerClient, CreateSecretCommand, DescribeSecretCommand } = require('@aws-sdk/client-secrets-manager'); +const { SFNClient, CreateStateMachineCommand, CreateActivityCommand, DescribeStateMachineCommand, DescribeActivityCommand } = require('@aws-sdk/client-sfn'); +const { IAMClient, AttachRolePolicyCommand, CreateRoleCommand } = require('@aws-sdk/client-iam') +const { LambdaClient, CreateFunctionCommand, GetEventSourceMappingCommand, CreateEventSourceMappingCommand, UpdateEventSourceMappingCommand } = require('@aws-sdk/client-lambda'); const _PORT = 8080; @@ -25,6 +32,7 @@ const _AWS_SDK_ENDPOINT = process.env.AWS_SDK_ENDPOINT; const _AWS_REGION = process.env.AWS_REGION; const _FAULT_ENDPOINT = 'http://fault.test:8080'; + process.env.AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || 'testcontainers-localstack'; process.env.AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || 'testcontainers-localstack'; @@ -63,6 +71,16 @@ async function prepareAwsServer() { region: _AWS_REGION, }); + const secretsClient = new SecretsManagerClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }) + + const snsClient = new SNSClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }) + // Set up S3 await s3Client.send( new CreateBucketCommand({ @@ -102,7 +120,7 @@ async function prepareAwsServer() { ); // Set up SQS - await sqsClient.send( + const sqsQueue = await sqsClient.send( new CreateQueueCommand({ QueueName: 'test_put_get_queue', }) @@ -113,11 +131,32 @@ async function prepareAwsServer() { new CreateStreamCommand({ StreamName: 'test_stream', ShardCount: 1, + })) + + // Set up SecretsManager + await secretsClient.send( + new CreateSecretCommand({ + "Description": "My test secret", + "Name": "MyTestSecret", + "SecretString": "{\"username\":\"user\",\"password\":\"password\"}" }) ); + + // Set up SNS + await snsClient.send(new CreateTopicCommand({ + "Name": "TestTopic" + })) + + // Set up Lambda + await setupLambda() + + // Set up StepFunctions + await setupSfn() + } catch (error) { console.error('Unexpected exception occurred', error); } + } const server = http.createServer(async (req, res) => { @@ -134,7 +173,7 @@ const server = http.createServer(async (req, res) => { res.writeHead(405); res.end(); } -}); +}) async function handleGetRequest(req, res, path) { if (path.includes('s3')) { @@ -147,6 +186,14 @@ async function handleGetRequest(req, res, path) { await handleKinesisRequest(req, res, path); } else if (path.includes('bedrock')) { await handleBedrockRequest(req, res, path); + } else if (path.includes('secretsmanager')) { + await handleSecretsRequest(req, res, path); + } else if (path.includes('stepfunctions')) { + await handleSfnRequest(req, res, path); + } else if (path.includes('sns')) { + await handleSnsRequest(req, res, path); + } else if (path.includes('lambda')) { + await handleLambdaRequest(req, res, path); } else { res.writeHead(404); res.end(); @@ -517,7 +564,9 @@ async function handleBedrockRequest(req, res, path) { await withInjected200Success( bedrockClient, ['GetGuardrailCommand'], - { guardrailId: 'bt4o77i015cu' }, + { guardrailId: 'bt4o77i015cu', + guardrailArn: 'arn:aws:bedrock:us-east-1:000000000000:guardrail/bt4o77i015cu' + }, async () => { await bedrockClient.send( new GetGuardrailCommand({ @@ -748,6 +797,349 @@ async function handleBedrockRequest(req, res, path) { res.end(); } + +async function handleSecretsRequest(req, res, path) { + const secretsClient = new SecretsManagerClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + if (path.includes(_ERROR)) { + res.statusCode = 400; + + try { + await secretsClient.send( + new DescribeSecretCommand({ + SecretId: "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret" + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + } + + if (path.includes(_FAULT)) { + res.statusCode = 500; + statusCodeForFault = 500; + + try { + const faultSecretsClient = new SecretsManagerClient({ + endpoint: _FAULT_ENDPOINT, + region: _AWS_REGION, + ...noRetryConfig, + }); + + await faultSecretsClient.send( + new DescribeSecretCommand({ + SecretId: "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret" + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + } + + if (path.includes('describesecret/my-secret')) { + await secretsClient.send( + new DescribeSecretCommand({ + SecretId: "MyTestSecret" + }) + ); + } + + res.end(); +} + +async function handleSfnRequest(req, res, path) { + const sfnClient = new SFNClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + if (path.includes(_ERROR)) { + res.statusCode = 400; + + try { + await sfnClient.send( + new DescribeStateMachineCommand({ + stateMachineArn: "arn:aws:states:us-west-2:000000000000:stateMachine:nonExistentStateMachine" + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + } + + if (path.includes(_FAULT)) { + res.statusCode = 500; + statusCodeForFault = 500; + + try { + + const faultSfnClient = new SFNClient({ + endpoint: _FAULT_ENDPOINT, + region: _AWS_REGION, + ...noRetryConfig, + }); + + await faultSfnClient.send( + new DescribeStateMachineCommand({ + stateMachineArn: "arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine" + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + } + + if (path.includes('describestatemachine/state-machine')) { + await sfnClient.send( + new DescribeStateMachineCommand({ + stateMachineArn: "arn:aws:states:us-west-2:000000000000:stateMachine:TestStateMachine" + }) + ); + } + + if (path.includes('describeactivity/activity')) { + await sfnClient.send( + new DescribeActivityCommand({ + activityArn: "arn:aws:states:us-west-2:000000000000:activity:TestActivity" + }) + ); + } + + res.end(); +} + +async function handleSnsRequest(req, res, path) { + const snsClient = new SNSClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + if (path.includes(_ERROR)) { + res.statusCode = 404; + + try { + await snsClient.send( + new GetTopicAttributesCommand({ + TopicArn: "arn:aws:sns:us-west-2:000000000000:nonExistentTopic", + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + + } + + if (path.includes(_FAULT)) { + res.statusCode = 500; + statusCodeForFault = 500; + + try { + const faultSnsClient = new SNSClient({ + endpoint: _FAULT_ENDPOINT, + region: _AWS_REGION, + ...noRetryConfig, + }); + + await faultSnsClient.send( + new GetTopicAttributesCommand({ + TopicArn: "arn:aws:sns:us-west-2:000000000000:invalidTopic" + }) + ); + } catch (err) { + console.log('Expected exception occurred', err); + } + } + + if (path.includes('gettopicattributes/topic')) { + await snsClient.send( + new GetTopicAttributesCommand({ + TopicArn: "arn:aws:sns:us-west-2:000000000000:TestTopic" + }) + ); + } + + res.end(); +} + +async function handleLambdaRequest(req, res, path) { + const lambdaClient = new LambdaClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + if (path.includes(_ERROR)) { + res.statusCode = 404; + + try { + await lambdaClient.send( + new GetEventSourceMappingCommand({ + UUID: "nonExistentUUID" + }) + ); + } + catch(err) { + console.log('Expected exception occurred', err); + } + + } + + if (path.includes(_FAULT)) { + res.statusCode = 500; + statusCodeForFault = 500; + + try { + const faultLambdaClient = new LambdaClient({ + endpoint: _FAULT_ENDPOINT, + region: _AWS_REGION, + ...noRetryConfig, + }); + + await faultLambdaClient.send( + new UpdateEventSourceMappingCommand({ + UUID: "123e4567-e89b-12d3-a456-426614174000" + }) + ); + } + catch(err) { + console.log('Expected exception occurred', err); + } + } + + + if (path.includes('geteventsourcemapping')) { + await lambdaClient.send( + new GetEventSourceMappingCommand({ + UUID: '' + }) + ); + } + res.end(); +} + +async function setupLambda() { + const lambdaClient = new LambdaClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + const iamClient = new IAMClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + const trustPolicy = { + Version: "2012-10-17", + Statement: [{ + Effect: "Allow", + Principal: { + Service: "lambda.amazonaws.com" + }, + Action: "sts:AssumeRole" + }] + }; + + const functionName = 'testFunction' + + const lambdaRoleParams = { + RoleName: "LambdaRole", + AssumeRolePolicyDocument: JSON.stringify(trustPolicy), + }; + + const policyParams = { + RoleName: "LambdaRole", + PolicyArn: "arn:aws:iam::aws:policy/AWSLambda_FullAccess" + }; + + const role = await iamClient.send(new CreateRoleCommand(lambdaRoleParams)); + await iamClient.send(new AttachRolePolicyCommand(policyParams)); + + const zip = new JSZip(); + zip.file('index.js', 'exports.handler = async (event) => { return { statusCode: 200 }; };'); + const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' }); + + const functionParams = { + Code: { + ZipFile: zipBuffer + }, + FunctionName: functionName, + Handler: "index.handler", + Role: role.Role.Arn, + Runtime: "nodejs18.x" + }; + + const mappingParams = { + EventSourceArn: "arn:aws:sns:us-west-2:000000000000:TestTopic", + FunctionName: functionName, + Enabled: false + } + + await lambdaClient.send(new CreateFunctionCommand(functionParams)); + await lambdaClient.send(new CreateEventSourceMappingCommand(mappingParams)); +} + +async function setupSfn() { + const sfnClient = new SFNClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + const iamClient = new IAMClient({ + endpoint: _AWS_SDK_ENDPOINT, + region: _AWS_REGION, + }); + + const trustPolicy = { + Version: "2012-10-17", + Statement: [{ + Effect: "Allow", + Principal: { + Service: "states.amazonaws.com" + }, + Action: "sts:AssumeRole" + }] +}; + +const roleName = 'testRole' + +const createRoleResponse = await iamClient.send(new CreateRoleCommand({ + RoleName: roleName, + AssumeRolePolicyDocument: JSON.stringify(trustPolicy), +})); + +await iamClient.send(new AttachRolePolicyCommand({ + RoleName: roleName, + PolicyArn: 'arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess' +})); + +const roleArn = createRoleResponse.Role.Arn + +const definition = { + StartAt: "HelloWorld", + States: { + "HelloWorld": { + Type: "Pass", + Result: "Hello, World!", + End: true + } + } +}; + +await sfnClient.send(new CreateStateMachineCommand({ + name: 'TestStateMachine', + definition: JSON.stringify(definition), + roleArn: roleArn, + type: 'STANDARD' +})); + +await sfnClient.send( + new CreateActivityCommand({ + name: 'TestActivity', + })); +} + function inject200Success(client, commandNames, additionalResponse = {}, middlewareName = 'inject200SuccessMiddleware') { const middleware = (next, context) => async (args) => { const { commandName } = context; @@ -775,12 +1167,9 @@ async function withInjected200Success(client, commandNames, additionalResponse, client.middlewareStack.remove(middlewareName); } - - prepareAwsServer().then(() => { server.listen(_PORT, '0.0.0.0', () => { console.log('Server is listening on port', _PORT); console.log('Ready'); }); -}); - +}); \ No newline at end of file diff --git a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py index 31f72d2..c7cac2a 100644 --- a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py +++ b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 from logging import INFO, Logger, getLogger import math +import re from typing import Dict, List from docker.types import EndpointConfig @@ -18,6 +19,7 @@ AWS_REMOTE_RESOURCE_TYPE, AWS_REMOTE_SERVICE, AWS_SPAN_KIND, + AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER ) from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue from opentelemetry.proto.metrics.v1.metrics_pb2 import ExponentialHistogramDataPoint, Metric @@ -32,8 +34,14 @@ _AWS_KINESIS_STREAM_NAME: str = "aws.kinesis.stream.name" _AWS_BEDROCK_AGENT_ID: str = "aws.bedrock.agent.id" _AWS_BEDROCK_GUARDRAIL_ID: str = "aws.bedrock.guardrail.id" +_AWS_BEDROCK_GUARDRAIL_ARN: str = "aws.bedrock.guardrail.arn" _AWS_BEDROCK_KNOWLEDGE_BASE_ID: str = "aws.bedrock.knowledge_base.id" _AWS_BEDROCK_DATA_SOURCE_ID: str = "aws.bedrock.data_source.id" +_AWS_SECRET_ARN: str = "aws.secretsmanager.secret.arn" +_AWS_SNS_TOPIC_ARN: str = 'aws.sns.topic.arn' +_AWS_LAMBDA_RESOURCE_MAPPING_ID: str = 'aws.lambda.resource_mapping.id' +_AWS_STATE_MACHINE_ARN: str = "aws.stepfunctions.state_machine.arn" +_AWS_ACTIVITY_ARN: str = "aws.stepfunctions.activity.arn" _GEN_AI_REQUEST_MODEL: str = "gen_ai.request.model" _GEN_AI_REQUEST_TEMPERATURE: str = "gen_ai.request.temperature" _GEN_AI_REQUEST_TOP_P: str = "gen_ai.request.top_p" @@ -77,8 +85,9 @@ def set_up_dependency_container(cls): cls._local_stack: LocalStackContainer = ( LocalStackContainer(image="localstack/localstack:3.5.0") .with_name("localstack") - .with_services("s3", "sqs", "dynamodb", "kinesis") + .with_services("s3", "sqs", "dynamodb", "kinesis", 'secretsmanager', 'stepfunctions', 'iam', 'sns', "lambda") .with_env("DEFAULT_REGION", "us-west-2") + .with_volume_mapping("/var/run/docker.sock", "/var/run/docker.sock") .with_kwargs(network=NETWORK_NAME, networking_config=local_stack_networking_config) ) cls._local_stack.start() @@ -104,6 +113,7 @@ def test_s3_create_bucket(self): remote_operation="CreateBucket", remote_resource_type="AWS::S3::Bucket", remote_resource_identifier="test-bucket-name", + cloudformation_primary_identifier="test-bucket-name", request_specific_attributes={ SpanAttributes.AWS_S3_BUCKET: "test-bucket-name", }, @@ -122,6 +132,7 @@ def test_s3_create_object(self): remote_operation="PutObject", remote_resource_type="AWS::S3::Bucket", remote_resource_identifier="test-put-object-bucket-name", + cloudformation_primary_identifier="test-put-object-bucket-name", request_specific_attributes={ SpanAttributes.AWS_S3_BUCKET: "test-put-object-bucket-name", }, @@ -140,6 +151,7 @@ def test_s3_get_object(self): remote_operation="GetObject", remote_resource_type="AWS::S3::Bucket", remote_resource_identifier="test-get-object-bucket-name", + cloudformation_primary_identifier="test-get-object-bucket-name", request_specific_attributes={ SpanAttributes.AWS_S3_BUCKET: "test-get-object-bucket-name", }, @@ -158,6 +170,7 @@ def test_s3_error(self): remote_operation="CreateBucket", remote_resource_type="AWS::S3::Bucket", remote_resource_identifier="-", + cloudformation_primary_identifier="-", request_specific_attributes={ SpanAttributes.AWS_S3_BUCKET: "-", }, @@ -178,6 +191,7 @@ def test_s3_fault(self): remote_operation="CreateBucket", remote_resource_type="AWS::S3::Bucket", remote_resource_identifier="valid-bucket-name", + cloudformation_primary_identifier="valid-bucket-name", request_specific_attributes={ SpanAttributes.AWS_S3_BUCKET: "valid-bucket-name", }, @@ -196,6 +210,7 @@ def test_dynamodb_create_table(self): remote_operation="CreateTable", remote_resource_type="AWS::DynamoDB::Table", remote_resource_identifier="test_table", + cloudformation_primary_identifier="test_table", request_specific_attributes={ SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: ["test_table"], }, @@ -214,6 +229,7 @@ def test_dynamodb_put_item(self): remote_operation="PutItem", remote_resource_type="AWS::DynamoDB::Table", remote_resource_identifier="put_test_table", + cloudformation_primary_identifier="put_test_table", request_specific_attributes={ SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: ["put_test_table"], }, @@ -232,6 +248,7 @@ def test_dynamodb_error(self): remote_operation="PutItem", remote_resource_type="AWS::DynamoDB::Table", remote_resource_identifier="invalid_table", + cloudformation_primary_identifier="invalid_table", request_specific_attributes={ SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: ["invalid_table"], }, @@ -252,6 +269,7 @@ def test_dynamodb_fault(self): remote_operation="PutItem", remote_resource_type="AWS::DynamoDB::Table", remote_resource_identifier="invalid_table", + cloudformation_primary_identifier="invalid_table", request_specific_attributes={ SpanAttributes.AWS_DYNAMODB_TABLE_NAMES: ["invalid_table"], }, @@ -270,6 +288,7 @@ def test_sqs_create_queue(self): remote_operation="CreateQueue", remote_resource_type="AWS::SQS::Queue", remote_resource_identifier="test_queue", + cloudformation_primary_identifier="test_queue", request_specific_attributes={ _AWS_SQS_QUEUE_NAME: "test_queue", }, @@ -289,6 +308,7 @@ def test_sqs_send_message(self): remote_operation="SendMessage", remote_resource_type="AWS::SQS::Queue", remote_resource_identifier="test_put_get_queue", + cloudformation_primary_identifier="http://localstack:4566/000000000000/test_put_get_queue", request_specific_attributes={ _AWS_SQS_QUEUE_URL: "http://localstack:4566/000000000000/test_put_get_queue", }, @@ -309,6 +329,7 @@ def test_sqs_receive_message(self): remote_operation="ReceiveMessage", remote_resource_type="AWS::SQS::Queue", remote_resource_identifier="test_put_get_queue", + cloudformation_primary_identifier="http://localstack:4566/000000000000/test_put_get_queue", request_specific_attributes={ _AWS_SQS_QUEUE_URL: "http://localstack:4566/000000000000/test_put_get_queue", }, @@ -329,6 +350,7 @@ def test_sqs_error(self): remote_operation="SendMessage", remote_resource_type="AWS::SQS::Queue", remote_resource_identifier="sqserror", + cloudformation_primary_identifier="http://error.test:8080/000000000000/sqserror", request_specific_attributes={ _AWS_SQS_QUEUE_URL: "http://error.test:8080/000000000000/sqserror", }, @@ -350,6 +372,7 @@ def test_sqs_fault(self): remote_operation="CreateQueue", remote_resource_type="AWS::SQS::Queue", remote_resource_identifier="invalid_test", + cloudformation_primary_identifier="invalid_test", request_specific_attributes={ _AWS_SQS_QUEUE_NAME: "invalid_test", }, @@ -368,6 +391,7 @@ def test_kinesis_put_record(self): remote_operation="PutRecord", remote_resource_type="AWS::Kinesis::Stream", remote_resource_identifier="test_stream", + cloudformation_primary_identifier="test_stream", request_specific_attributes={ _AWS_KINESIS_STREAM_NAME: "test_stream", }, @@ -386,6 +410,7 @@ def test_kinesis_error(self): remote_operation="PutRecord", remote_resource_type="AWS::Kinesis::Stream", remote_resource_identifier="invalid_stream", + cloudformation_primary_identifier="invalid_stream", request_specific_attributes={ _AWS_KINESIS_STREAM_NAME: "invalid_stream", }, @@ -406,6 +431,7 @@ def test_kinesis_fault(self): remote_operation="PutRecord", remote_resource_type="AWS::Kinesis::Stream", remote_resource_identifier="test_stream", + cloudformation_primary_identifier="test_stream", request_specific_attributes={ _AWS_KINESIS_STREAM_NAME: "test_stream", }, @@ -425,6 +451,7 @@ def test_bedrock_runtime_invoke_model_amazon_titan(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='amazon.titan-text-premier-v1:0', + cloudformation_primary_identifier="amazon.titan-text-premier-v1:0", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'amazon.titan-text-premier-v1:0', _GEN_AI_REQUEST_MAX_TOKENS: 3072, @@ -453,6 +480,7 @@ def test_bedrock_runtime_invoke_model_anthropic_claude(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='anthropic.claude-v2:1', + cloudformation_primary_identifier="anthropic.claude-v2:1", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'anthropic.claude-v2:1', _GEN_AI_REQUEST_MAX_TOKENS: 1000, @@ -480,6 +508,7 @@ def test_bedrock_runtime_invoke_model_meta_llama(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='meta.llama2-13b-chat-v1', + cloudformation_primary_identifier="meta.llama2-13b-chat-v1", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'meta.llama2-13b-chat-v1', _GEN_AI_REQUEST_MAX_TOKENS: 512, @@ -507,6 +536,7 @@ def test_bedrock_runtime_invoke_model_cohere_command_r(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='cohere.command-r-v1:0', + cloudformation_primary_identifier="cohere.command-r-v1:0", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'cohere.command-r-v1:0', _GEN_AI_REQUEST_MAX_TOKENS: 512, @@ -535,6 +565,7 @@ def test_bedrock_runtime_invoke_model_cohere_command(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='cohere.command-light-text-v14', + cloudformation_primary_identifier="cohere.command-light-text-v14", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'cohere.command-light-text-v14', _GEN_AI_REQUEST_MAX_TOKENS: 512, @@ -562,6 +593,7 @@ def test_bedrock_runtime_invoke_model_ai21_jamba(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='ai21.jamba-1-5-large-v1:0', + cloudformation_primary_identifier="ai21.jamba-1-5-large-v1:0", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'ai21.jamba-1-5-large-v1:0', _GEN_AI_REQUEST_MAX_TOKENS: 512, @@ -589,6 +621,7 @@ def test_bedrock_runtime_invoke_model_mistral_mistral(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='mistral.mistral-7b-instruct-v0:2', + cloudformation_primary_identifier="mistral.mistral-7b-instruct-v0:2", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'mistral.mistral-7b-instruct-v0:2', _GEN_AI_REQUEST_MAX_TOKENS: 4096, @@ -616,8 +649,10 @@ def test_bedrock_get_guardrail(self): remote_operation="GetGuardrail", remote_resource_type="AWS::Bedrock::Guardrail", remote_resource_identifier="bt4o77i015cu", + cloudformation_primary_identifier="arn:aws:bedrock:us-east-1:000000000000:guardrail/bt4o77i015cu", request_specific_attributes={ _AWS_BEDROCK_GUARDRAIL_ID: "bt4o77i015cu", + _AWS_BEDROCK_GUARDRAIL_ARN: "arn:aws:bedrock:us-east-1:000000000000:guardrail/bt4o77i015cu" }, span_name="Bedrock.GetGuardrail", ) @@ -635,6 +670,7 @@ def test_bedrock_agent_runtime_invoke_agent(self): remote_operation="InvokeAgent", remote_resource_type="AWS::Bedrock::Agent", remote_resource_identifier="Q08WFRPHVL", + cloudformation_primary_identifier="Q08WFRPHVL", request_specific_attributes={ _AWS_BEDROCK_AGENT_ID: "Q08WFRPHVL", }, @@ -654,6 +690,7 @@ def test_bedrock_agent_runtime_retrieve(self): remote_operation="Retrieve", remote_resource_type="AWS::Bedrock::KnowledgeBase", remote_resource_identifier="test-knowledge-base-id", + cloudformation_primary_identifier="test-knowledge-base-id", request_specific_attributes={ _AWS_BEDROCK_KNOWLEDGE_BASE_ID: "test-knowledge-base-id", }, @@ -673,6 +710,7 @@ def test_bedrock_agent_get_agent(self): remote_operation="GetAgent", remote_resource_type="AWS::Bedrock::Agent", remote_resource_identifier="TESTAGENTID", + cloudformation_primary_identifier="TESTAGENTID", request_specific_attributes={ _AWS_BEDROCK_AGENT_ID: "TESTAGENTID", }, @@ -692,6 +730,7 @@ def test_bedrock_agent_get_knowledge_base(self): remote_operation="GetKnowledgeBase", remote_resource_type="AWS::Bedrock::KnowledgeBase", remote_resource_identifier="invalid-knowledge-base-id", + cloudformation_primary_identifier="invalid-knowledge-base-id", request_specific_attributes={ _AWS_BEDROCK_KNOWLEDGE_BASE_ID: "invalid-knowledge-base-id", }, @@ -711,12 +750,263 @@ def test_bedrock_agent_get_data_source(self): remote_operation="GetDataSource", remote_resource_type="AWS::Bedrock::DataSource", remote_resource_identifier="DATASURCID", + cloudformation_primary_identifier=r'TESTKBSEID\|DATASURCID', request_specific_attributes={ _AWS_BEDROCK_DATA_SOURCE_ID: "DATASURCID", }, span_name="BedrockAgent.GetDataSource", ) + def test_secretsmanager_fault(self): + self.do_test_requests( + "secretsmanager/fault", + "GET", + 500, + 0, + 1, + dp_count=3, + local_operation="GET /secretsmanager", + local_operation_2="POST /", + rpc_service="SecretsManager", + remote_service="AWS::SecretsManager", + remote_operation="DescribeSecret", + remote_resource_type="AWS::SecretsManager::Secret", + remote_resource_identifier="nonExistentSecret", + cloudformation_primary_identifier="arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret", + request_specific_attributes= { + _AWS_SECRET_ARN: "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret", + }, + span_name="SecretsManager.DescribeSecret", + ) + + def test_secretsmanager_error(self): + self.do_test_requests( + "secretsmanager/error", + "GET", + 400, + 1, + 0, + local_operation="GET /secretsmanager", + rpc_service="SecretsManager", + remote_service="AWS::SecretsManager", + remote_operation="DescribeSecret", + remote_resource_type="AWS::SecretsManager::Secret", + remote_resource_identifier="nonExistentSecret", + cloudformation_primary_identifier="arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret", + request_specific_attributes= { + _AWS_SECRET_ARN: "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonExistentSecret", + }, + span_name="SecretsManager.DescribeSecret", + ) + + def test_secretsmanager_describe_secret(self): + self.do_test_requests( + "secretsmanager/describesecret/my-secret", + "GET", + 200, + 0, + 0, + local_operation="GET /secretsmanager", + rpc_service="SecretsManager", + remote_service="AWS::SecretsManager", + remote_operation="DescribeSecret", + remote_resource_type="AWS::SecretsManager::Secret", + remote_resource_identifier=r'MyTestSecret-[a-zA-Z0-9]{6}$', + cloudformation_primary_identifier=r'arn:aws:secretsmanager:us-west-2:000000000000:secret:MyTestSecret-[a-zA-Z0-9]{6}$', + response_specific_attributes= { + _AWS_SECRET_ARN: r'arn:aws:secretsmanager:us-west-2:000000000000:secret:MyTestSecret-[a-zA-Z0-9]{6}$', + }, + span_name="SecretsManager.DescribeSecret", + ) + + def test_stepfunctions_fault(self): + self.do_test_requests( + "stepfunctions/fault", + "GET", + 500, + 0, + 1, + dp_count=3, + local_operation="GET /stepfunctions", + local_operation_2="POST /", + rpc_service="SFN", + remote_service="AWS::StepFunctions", + remote_operation="DescribeStateMachine", + remote_resource_type="AWS::StepFunctions::StateMachine", + remote_resource_identifier="invalid-state-machine", + cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine", + request_specific_attributes= { + _AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:invalid-state-machine", + }, + span_name="SFN.DescribeStateMachine", + ) + + def test_stepfunctions_error(self): + self.do_test_requests( + "stepfunctions/error", + "GET", + 400, + 1, + 0, + local_operation="GET /stepfunctions", + rpc_service="SFN", + remote_service="AWS::StepFunctions", + remote_operation="DescribeStateMachine", + remote_resource_type="AWS::StepFunctions::StateMachine", + remote_resource_identifier="nonExistentStateMachine", + cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:nonExistentStateMachine", + request_specific_attributes= { + _AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:nonExistentStateMachine", + }, + span_name="SFN.DescribeStateMachine", + ) + + def test_stepfunctions_describe_state_machine(self): + self.do_test_requests( + "stepfunctions/describestatemachine/state-machine", + "GET", + 200, + 0, + 0, + local_operation="GET /stepfunctions", + rpc_service="SFN", + remote_service="AWS::StepFunctions", + remote_operation="DescribeStateMachine", + remote_resource_type="AWS::StepFunctions::StateMachine", + remote_resource_identifier="TestStateMachine", + cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:stateMachine:TestStateMachine", + request_specific_attributes= { + _AWS_STATE_MACHINE_ARN: "arn:aws:states:us-west-2:000000000000:stateMachine:TestStateMachine", + }, + span_name="SFN.DescribeStateMachine", + ) + + def test_stepfunctions_describe_activity(self): + self.do_test_requests( + "stepfunctions/describeactivity/activity", + "GET", + 200, + 0, + 0, + local_operation="GET /stepfunctions", + rpc_service="SFN", + remote_service="AWS::StepFunctions", + remote_operation="DescribeActivity", + remote_resource_type="AWS::StepFunctions::Activity", + remote_resource_identifier="TestActivity", + cloudformation_primary_identifier="arn:aws:states:us-west-2:000000000000:activity:TestActivity", + request_specific_attributes= { + _AWS_ACTIVITY_ARN: "arn:aws:states:us-west-2:000000000000:activity:TestActivity", + }, + span_name="SFN.DescribeActivity", + ) + + def test_sns_fault(self): + self.do_test_requests( + "sns/fault", + "GET", + 500, + 0, + 1, + dp_count=3, + local_operation="GET /sns", + local_operation_2="POST /", + rpc_service="SNS", + remote_service="AWS::SNS", + remote_operation="GetTopicAttributes", + remote_resource_type="AWS::SNS::Topic", + remote_resource_identifier="invalidTopic", + cloudformation_primary_identifier="arn:aws:sns:us-west-2:000000000000:invalidTopic", + request_specific_attributes= { + _AWS_SNS_TOPIC_ARN: "arn:aws:sns:us-west-2:000000000000:invalidTopic", + }, + span_name="SNS GetTopicAttributes", + ) + + def test_sns_error(self): + self.do_test_requests( + "sns/error", + "GET", + 404, # this is the expected status code error for sns + 1, + 0, + local_operation="GET /sns", + rpc_service="SNS", + remote_service="AWS::SNS", + remote_operation="GetTopicAttributes", + remote_resource_type="AWS::SNS::Topic", + remote_resource_identifier="nonExistentTopic", + cloudformation_primary_identifier="arn:aws:sns:us-west-2:000000000000:nonExistentTopic", + request_specific_attributes= { + _AWS_SNS_TOPIC_ARN: "arn:aws:sns:us-west-2:000000000000:nonExistentTopic", + }, + span_name="SNS GetTopicAttributes", + ) + + def test_sns_get_topic_attributes(self): + self.do_test_requests( + "sns/gettopicattributes/topic", + "GET", + 200, + 0, + 0, + local_operation="GET /sns", + rpc_service="SNS", + remote_service="AWS::SNS", + remote_operation="GetTopicAttributes", + remote_resource_type="AWS::SNS::Topic", + remote_resource_identifier="TestTopic", + cloudformation_primary_identifier="arn:aws:sns:us-west-2:000000000000:TestTopic", + request_specific_attributes= { + _AWS_SNS_TOPIC_ARN: "arn:aws:sns:us-west-2:000000000000:TestTopic", + }, + span_name="SNS GetTopicAttributes", + ) + + def test_lambda_fault(self): + self.do_test_requests( + "lambda/fault", + "GET", + 500, + 0, + 1, + dp_count=3, + local_operation="GET /lambda", + local_operation_2="PUT /2015-03-31", + rpc_service="Lambda", + remote_service="AWS::Lambda", + remote_operation="UpdateEventSourceMapping", + remote_resource_type="AWS::Lambda::EventSourceMapping", + remote_resource_identifier="123e4567-e89b-12d3-a456-426614174000", + cloudformation_primary_identifier="123e4567-e89b-12d3-a456-426614174000", + request_specific_attributes= { + _AWS_LAMBDA_RESOURCE_MAPPING_ID: "123e4567-e89b-12d3-a456-426614174000", + }, + span_name="Lambda.UpdateEventSourceMapping", + ) + + def test_lambda_error(self): + self.do_test_requests( + "lambda/error", + "GET", + 404, + 1, + 0, + local_operation="GET /lambda", + rpc_service="Lambda", + remote_service="AWS::Lambda", + remote_operation="GetEventSourceMapping", + remote_resource_type="AWS::Lambda::EventSourceMapping", + remote_resource_identifier="nonExistentUUID", + cloudformation_primary_identifier="nonExistentUUID", + request_specific_attributes= { + _AWS_LAMBDA_RESOURCE_MAPPING_ID: "nonExistentUUID", + }, + span_name="Lambda.GetEventSourceMapping", + ) + + #TODO: Need to add test_lambda_get_event_source_mapping once workaround is figured out for storing UUID between tests + @override def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs) -> None: target_spans: List[Span] = [] @@ -742,6 +1032,7 @@ def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSp span_kind, kwargs.get("remote_resource_type", "None"), kwargs.get("remote_resource_identifier", "None"), + kwargs.get("cloudformation_primary_identifier", "None"), ) def _assert_aws_attributes( @@ -753,6 +1044,7 @@ def _assert_aws_attributes( span_kind: str, remote_resource_type: str, remote_resource_identifier: str, + cloudformation_primary_identifier: str ) -> None: attributes_dict: Dict[str, AnyValue] = self._get_attributes_dict(attributes_list) self._assert_str_attribute(attributes_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) @@ -760,16 +1052,19 @@ def _assert_aws_attributes( self._assert_str_attribute(attributes_dict, AWS_REMOTE_SERVICE, remote_service) self._assert_str_attribute(attributes_dict, AWS_REMOTE_OPERATION, remote_operation) if remote_resource_type != "None": - self._assert_str_attribute(attributes_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) + self._assert_attribute(attributes_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) if remote_resource_identifier != "None": - self._assert_str_attribute(attributes_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) + self._assert_attribute(attributes_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) + if cloudformation_primary_identifier != "None": + self._assert_attribute(attributes_dict, AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformation_primary_identifier) + self._assert_str_attribute(attributes_dict, AWS_SPAN_KIND, span_kind) @override def _assert_semantic_conventions_span_attributes( self, resource_scope_spans: List[ResourceScopeSpan], method: str, path: str, status_code: int, **kwargs ) -> None: - + target_spans: List[Span] = [] for resource_scope_span in resource_scope_spans: # pylint: disable=no-member @@ -786,7 +1081,7 @@ def _assert_semantic_conventions_span_attributes( status_code, kwargs.get("request_specific_attributes", {}), kwargs.get("response_specific_attributes", {}), - ) + ) # pylint: disable=unidiomatic-typecheck def _assert_semantic_conventions_attributes( @@ -805,19 +1100,11 @@ def _assert_semantic_conventions_attributes( self._assert_int_attribute(attributes_dict, SpanAttributes.HTTP_STATUS_CODE, status_code) # TODO: aws sdk instrumentation is not respecting PEER_SERVICE # self._assert_str_attribute(attributes_dict, SpanAttributes.PEER_SERVICE, "backend:8080") - self._assert_specific_attributes(attributes_dict, request_specific_attributes) - self._assert_specific_attributes(attributes_dict, response_specific_attributes) - - def _assert_specific_attributes(self, attributes_dict: Dict[str, AnyValue], specific_attributes: Dict[str, AnyValue]) -> None: - for key, value in specific_attributes.items(): - if isinstance(value, str): - self._assert_str_attribute(attributes_dict, key, value) - elif isinstance(value, int): - self._assert_int_attribute(attributes_dict, key, value) - elif isinstance(value, float): - self._assert_float_attribute(attributes_dict, key, value) - else: - self._assert_array_value_ddb_table_name(attributes_dict, key, value) + for key, value in request_specific_attributes.items(): + self._assert_attribute(attributes_dict, key, value) + + for key, value in response_specific_attributes.items(): + self._assert_attribute(attributes_dict, key, value) @override def _assert_metric_attributes( @@ -845,23 +1132,23 @@ def _assert_metric_attributes( dependency_dp = dp_list[1] service_dp = dp_list[0] attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dependency_dp.attributes) - self._assert_str_attribute(attribute_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) - self._assert_str_attribute(attribute_dict, AWS_LOCAL_OPERATION, kwargs.get("local_operation")) - self._assert_str_attribute(attribute_dict, AWS_REMOTE_SERVICE, kwargs.get("remote_service")) - self._assert_str_attribute(attribute_dict, AWS_REMOTE_OPERATION, kwargs.get("remote_operation")) - self._assert_str_attribute(attribute_dict, AWS_SPAN_KIND, kwargs.get("dependency_metric_span_kind") or "CLIENT") + self._assert_attribute(attribute_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) + self._assert_attribute(attribute_dict, AWS_LOCAL_OPERATION, kwargs.get("local_operation")) + self._assert_attribute(attribute_dict, AWS_REMOTE_SERVICE, kwargs.get("remote_service")) + self._assert_attribute(attribute_dict, AWS_REMOTE_OPERATION, kwargs.get("remote_operation")) + self._assert_attribute(attribute_dict, AWS_SPAN_KIND, kwargs.get("dependency_metric_span_kind") or "CLIENT") remote_resource_type = kwargs.get("remote_resource_type", "None") remote_resource_identifier = kwargs.get("remote_resource_identifier", "None") if remote_resource_type != "None": - self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) + self._assert_attribute(attribute_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) if remote_resource_identifier != "None": - self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) + self._assert_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) self.check_sum(metric_name, dependency_dp.sum, expected_sum) attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(service_dp.attributes) - self._assert_str_attribute(attribute_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) - self._assert_str_attribute(attribute_dict, AWS_LOCAL_OPERATION, kwargs.get("local_operation")) - self._assert_str_attribute(attribute_dict, AWS_SPAN_KIND, "LOCAL_ROOT") + self._assert_attribute(attribute_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) + self._assert_attribute(attribute_dict, AWS_LOCAL_OPERATION, kwargs.get("local_operation")) + self._assert_attribute(attribute_dict, AWS_SPAN_KIND, "LOCAL_ROOT") self.check_sum(metric_name, service_dp.sum, expected_sum) else: dependency_dp: ExponentialHistogramDataPoint = max(dp_list, key=lambda dp: len(dp.attributes)) @@ -878,9 +1165,9 @@ def _assert_metric_attributes( remote_resource_type = kwargs.get("remote_resource_type", "None") remote_resource_identifier = kwargs.get("remote_resource_identifier", "None") if remote_resource_type != "None": - self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) + self._assert_attribute(attribute_dict, AWS_REMOTE_RESOURCE_TYPE, remote_resource_type) if remote_resource_identifier != "None": - self._assert_str_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) + self._assert_attribute(attribute_dict, AWS_REMOTE_RESOURCE_IDENTIFIER, remote_resource_identifier) self.check_sum(metric_name, dependency_dp.sum, expected_sum) attribute_dict_service: Dict[str, AnyValue] = self._get_attributes_dict(service_dp.attributes) @@ -902,10 +1189,29 @@ def _assert_metric_attributes( self._assert_str_attribute(attribute_dict_other, AWS_SPAN_KIND, "LOCAL_ROOT") self.check_sum(metric_name, other_dp.sum, expected_sum) + def _assert_attribute(self, attributes_dict: Dict[str, AnyValue], key, value) -> None: + if isinstance(value, str): + self._assert_str_attribute(attributes_dict, key, value) + elif isinstance(value, int): + self._assert_int_attribute(attributes_dict, key, value) + elif isinstance(value, float): + self._assert_float_attribute(attributes_dict, key, value) + else: + self._assert_array_value_ddb_table_name(attributes_dict, key, value) + + @override + def _assert_str_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, expected_value: str): + self.assertIn(key, attributes_dict) + actual_value: AnyValue = attributes_dict[key] + self.assertIsNotNone(actual_value) + pattern = re.compile(expected_value) + match = pattern.fullmatch(actual_value.string_value) + self.assertTrue(match is not None, f"Actual: {actual_value.string_value} does not match Expected: {expected_value}") + # pylint: disable=consider-using-enumerate def _assert_array_value_ddb_table_name(self, attributes_dict: Dict[str, AnyValue], key: str, expect_values: list): self.assertIn(key, attributes_dict) actual_values: [AnyValue] = attributes_dict[key].array_value self.assertEqual(len(actual_values.values), len(expect_values)) for index in range(len(actual_values.values)): - self.assertEqual(actual_values.values[index].string_value, expect_values[index]) + self.assertEqual(actual_values.values[index].string_value, expect_values[index]) \ No newline at end of file diff --git a/contract-tests/tests/test/amazon/base/contract_test_base.py b/contract-tests/tests/test/amazon/base/contract_test_base.py index ea522f8..07a59ab 100644 --- a/contract-tests/tests/test/amazon/base/contract_test_base.py +++ b/contract-tests/tests/test/amazon/base/contract_test_base.py @@ -1,6 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import time +import re from logging import INFO, Logger, getLogger from typing import Dict, List from unittest import TestCase @@ -229,7 +230,7 @@ def _assert_int_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, actual_value: AnyValue = attributes_dict[key] self.assertIsNotNone(actual_value) self.assertEqual(expected_value, actual_value.int_value) - + def _assert_float_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, expected_value: float) -> None: self.assertIn(key, attributes_dict) actual_value: AnyValue = attributes_dict[key] @@ -285,4 +286,4 @@ def _assert_semantic_conventions_span_attributes( def _assert_metric_attributes( self, resource_scope_metrics: List[ResourceScopeMetric], metric_name: str, expected_sum: int, **kwargs ): - self.fail("Tests must implement this function") + self.fail("Tests must implement this function") \ No newline at end of file diff --git a/contract-tests/tests/test/amazon/utils/application_signals_constants.py b/contract-tests/tests/test/amazon/utils/application_signals_constants.py index 9f3a625..14b602e 100644 --- a/contract-tests/tests/test/amazon/utils/application_signals_constants.py +++ b/contract-tests/tests/test/amazon/utils/application_signals_constants.py @@ -17,4 +17,5 @@ AWS_REMOTE_OPERATION: str = "aws.remote.operation" AWS_REMOTE_RESOURCE_TYPE: str = "aws.remote.resource.type" AWS_REMOTE_RESOURCE_IDENTIFIER: str = "aws.remote.resource.identifier" +AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER: str = 'aws.remote.resource.cfn.primary.identifier' AWS_SPAN_KIND: str = "aws.span.kind" From 5dd389e052eb59d8a25726ca72551eddd0a4ba99 Mon Sep 17 00:00:00 2001 From: Steve Liu Date: Mon, 16 Dec 2024 16:07:54 -0800 Subject: [PATCH 5/7] feat: Support Gen AI attributes for Amazon Nova foundational model (#133) *Description of changes:* Added GenAI inference parameters auto instrumentation support for Amazon Nova. image Contract test: image Unit test: image By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- .../src/patches/aws/services/bedrock.ts | 22 ++++++++ .../test/patches/aws/services/bedrock.test.ts | 54 +++++++++++++++++++ .../images/applications/aws-sdk/server.js | 24 ++++++++- .../tests/test/amazon/aws-sdk/aws_sdk_test.py | 47 ++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts index 82da47a..68fa3fe 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts @@ -232,6 +232,16 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] = requestBody.textGenerationConfig.maxTokenCount; } + } else if (modelId.includes('amazon.nova')) { + if (requestBody.inferenceConfig?.temperature !== undefined) { + spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE] = requestBody.inferenceConfig.temperature; + } + if (requestBody.inferenceConfig?.top_p !== undefined) { + spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TOP_P] = requestBody.inferenceConfig.top_p; + } + if (requestBody.inferenceConfig?.max_new_tokens !== undefined) { + spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] = requestBody.inferenceConfig.max_new_tokens; + } } else if (modelId.includes('anthropic.claude')) { if (requestBody.max_tokens !== undefined) { spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens; @@ -335,6 +345,18 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { responseBody.results[0].completionReason, ]); } + } else if (currentModelId.includes('amazon.nova')) { + if (responseBody.usage !== undefined) { + if (responseBody.usage.inputTokens !== undefined) { + span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.inputTokens); + } + if (responseBody.usage.outputTokens !== undefined) { + span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.usage.outputTokens); + } + } + if (responseBody.stopReason !== undefined) { + span.setAttribute(AwsSpanProcessingUtil.GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.stopReason]); + } } else if (currentModelId.includes('anthropic.claude')) { if (responseBody.usage?.input_tokens !== undefined) { span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.input_tokens); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts index f642e09..1759774 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts @@ -400,6 +400,60 @@ describe('BedrockRuntime', () => { expect(invokeModelSpan.kind).toBe(SpanKind.CLIENT); }); + it('Add Amazon Nova model attributes to span', async () => { + const modelId: string = 'amazon.nova-pro-v1:0'; + const prompt: string = 'Campfire story'; + const mockRequestBody: string = JSON.stringify({ + inputText: prompt, + inferenceConfig: { + max_new_tokens: 500, + temperature: 0.9, + top_p: 0.7, + }, + }); + const mockResponseBody: any = { + output: { message: { content: [{ text: '' }], role: 'assistant' } }, + stopReason: 'max_tokens', + usage: { inputTokens: 432, outputTokens: 681 }, + + request: { + commandInput: { + modelId: modelId, + }, + }, + }; + + nock(`https://bedrock-runtime.${region}.amazonaws.com`) + .post(`/model/${encodeURIComponent(modelId)}/invoke`) + .reply(200, mockResponseBody); + + await bedrock + .invokeModel({ + modelId: modelId, + body: mockRequestBody, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const invokeModelSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockRuntime.InvokeModel'; + }); + expect(invokeModelSpans.length).toBe(1); + const invokeModelSpan = invokeModelSpans[0]; + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM]).toBe('aws.bedrock'); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(modelId); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS]).toBe(500); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE]).toBe(0.9); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TOP_P]).toBe(0.7); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS]).toBe(432); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_USAGE_OUTPUT_TOKENS]).toBe(681); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_RESPONSE_FINISH_REASONS]).toEqual(['max_tokens']); + expect(invokeModelSpan.kind).toBe(SpanKind.CLIENT); + }); + it('Add Anthropic Claude model attributes to span', async () => { const modelId: string = 'anthropic.claude-3-5-sonnet-20240620-v1:0'; const prompt: string = 'Complete this text. It was the best of times it was the worst...'; diff --git a/contract-tests/images/applications/aws-sdk/server.js b/contract-tests/images/applications/aws-sdk/server.js index 7352bfa..288bb96 100644 --- a/contract-tests/images/applications/aws-sdk/server.js +++ b/contract-tests/images/applications/aws-sdk/server.js @@ -631,7 +631,29 @@ async function handleBedrockRequest(req, res, path) { }, ], } - + } + + if (path.includes("amazon.nova")) { + + modelId = "amazon.nova-pro-v1:0" + + request_body = { + messages: [{role: "user", content: [{text: "A camping trip"}]}], + inferenceConfig: { + max_new_tokens: 800, + temperature: 0.9, + top_p: 0.7, + }, + } + + response_body = { + output: {message: {content: [{text: ""}], role: "assistant"}}, + stopReason: "max_tokens", + usage: { + inputTokens: 432, + outputTokens: 681 + }, + } } if (path.includes('anthropic.claude')) { diff --git a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py index c7cac2a..ba20402 100644 --- a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py +++ b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py @@ -466,6 +466,34 @@ def test_bedrock_runtime_invoke_model_amazon_titan(self): span_name="BedrockRuntime.InvokeModel" ) + + def test_bedrock_runtime_invoke_model_amazon_nova(self): + result = self.do_test_requests( + "bedrock/invokemodel/invoke-model/amazon.nova-pro-v1:0", + "GET", + 200, + 0, + 0, + local_operation="GET /bedrock", + rpc_service="BedrockRuntime", + remote_service="AWS::BedrockRuntime", + remote_operation="InvokeModel", + remote_resource_type="AWS::Bedrock::Model", + remote_resource_identifier='amazon.nova-pro-v1:0', + request_specific_attributes={ + _GEN_AI_REQUEST_MODEL: 'amazon.nova-pro-v1:0', + _GEN_AI_REQUEST_MAX_TOKENS: 800, + _GEN_AI_REQUEST_TEMPERATURE: 0.9, + _GEN_AI_REQUEST_TOP_P: 0.7 + }, + response_specific_attributes={ + _GEN_AI_RESPONSE_FINISH_REASONS: ['max_tokens'], + _GEN_AI_USAGE_INPUT_TOKENS: 432, + _GEN_AI_USAGE_OUTPUT_TOKENS: 681 + }, + + span_name="BedrockRuntime.InvokeModel" + ) def test_bedrock_runtime_invoke_model_anthropic_claude(self): self.do_test_requests( @@ -1105,6 +1133,25 @@ def _assert_semantic_conventions_attributes( for key, value in response_specific_attributes.items(): self._assert_attribute(attributes_dict, key, value) + + def _assert_attribute(self, attributes_dict: Dict[str, AnyValue], key, value) -> None: + if isinstance(value, str): + self._assert_str_attribute(attributes_dict, key, value) + elif isinstance(value, int): + self._assert_int_attribute(attributes_dict, key, value) + elif isinstance(value, float): + self._assert_float_attribute(attributes_dict, key, value) + else: + self._assert_array_value_ddb_table_name(attributes_dict, key, value) + + @override + def _assert_str_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, expected_value: str): + self.assertIn(key, attributes_dict) + actual_value: AnyValue = attributes_dict[key] + self.assertIsNotNone(actual_value) + pattern = re.compile(expected_value) + match = pattern.fullmatch(actual_value.string_value) + self.assertTrue(match is not None, f"Actual: {actual_value.string_value} does not match Expected: {expected_value}") @override def _assert_metric_attributes( From c7e77ecc002300ff4adea0d4168f88a9d1eb5888 Mon Sep 17 00:00:00 2001 From: Steve Liu Date: Mon, 16 Dec 2024 16:58:10 -0800 Subject: [PATCH 6/7] Update rustc version (#135) *Description of changes:* Bumping the version of rustc to 1.81 in our Dockerfile to fix any docker errors By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 33e0b8b..bfc1705 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN npm install aws-aws-distro-opentelemetry-node-autoinstrumentation-$(node -p RUN npm install # Stage 2: Build the cp-utility binary -FROM public.ecr.aws/docker/library/rust:1.75 as builder +FROM public.ecr.aws/docker/library/rust:1.81 as builder WORKDIR /usr/src/cp-utility COPY ./tools/cp-utility . From fec93905708118827018adbad02f971f4bc1e205 Mon Sep 17 00:00:00 2001 From: Steve Liu Date: Tue, 17 Dec 2024 11:11:30 -0800 Subject: [PATCH 7/7] Added missing cloudformation attribute assertion in nova contract test (#136) *Description of changes:* Added missing cloudformation_primary_identifier assertion in Amazon nova contract test By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py index ba20402..005e286 100644 --- a/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py +++ b/contract-tests/tests/test/amazon/aws-sdk/aws_sdk_test.py @@ -480,6 +480,7 @@ def test_bedrock_runtime_invoke_model_amazon_nova(self): remote_operation="InvokeModel", remote_resource_type="AWS::Bedrock::Model", remote_resource_identifier='amazon.nova-pro-v1:0', + cloudformation_primary_identifier="amazon.nova-pro-v1:0", request_specific_attributes={ _GEN_AI_REQUEST_MODEL: 'amazon.nova-pro-v1:0', _GEN_AI_REQUEST_MAX_TOKENS: 800,