diff --git a/.github/scripts/setup_tmp_layer_files.sh b/.github/scripts/setup_tmp_layer_files.sh index 77c5875734..9d6da9635a 100644 --- a/.github/scripts/setup_tmp_layer_files.sh +++ b/.github/scripts/setup_tmp_layer_files.sh @@ -6,7 +6,8 @@ npm init -y npm i \ @aws-lambda-powertools/logger@$VERSION \ @aws-lambda-powertools/metrics@$VERSION \ - @aws-lambda-powertools/tracer@$VERSION + @aws-lambda-powertools/tracer@$VERSION \ + @aws-lambda-powertools/parameters@$VERSION rm -rf node_modules/@types \ package.json \ package-lock.json diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 86b0f5afb6..e044615f1c 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -94,6 +94,8 @@ jobs: path: ./cdk-layer-stack/* # NOTE: upload-artifact does not inherit working-directory setting. if-no-files-found: error retention-days: 1 + - name: CDK deploy canary + run: npm run cdk -w layer -- deploy --app cdk.out --context region=${{ matrix.region }} 'CanaryStack' --require-approval never --verbose --outputs-file cdk-outputs.json update_layer_arn_docs: needs: deploy-cdk-stack permissions: diff --git a/layers/bin/layers.ts b/layers/bin/layers.ts index 902bd6de5f..e945a14aba 100644 --- a/layers/bin/layers.ts +++ b/layers/bin/layers.ts @@ -2,6 +2,7 @@ import 'source-map-support/register'; import { App } from 'aws-cdk-lib'; import { LayerPublisherStack } from '../src/layer-publisher-stack'; +import { CanaryStack } from 'layers/src/canary-stack'; const SSM_PARAM_LAYER_ARN = '/layers/powertools-layer-arn'; @@ -12,3 +13,9 @@ new LayerPublisherStack(app, 'LayerPublisherStack', { layerName: 'AWSLambdaPowertoolsTypeScript', ssmParameterLayerArn: SSM_PARAM_LAYER_ARN, }); + +new CanaryStack(app, 'CanaryStack', { + powertoolsPackageVersion: app.node.tryGetContext('PowertoolsPackageVersion'), + ssmParameterLayerArn: SSM_PARAM_LAYER_ARN, + layerName: 'AWSLambdaPowertoolsCanaryTypeScript', +}); diff --git a/layers/src/canary-stack.ts b/layers/src/canary-stack.ts new file mode 100644 index 0000000000..4847af21d3 --- /dev/null +++ b/layers/src/canary-stack.ts @@ -0,0 +1,87 @@ +import { CustomResource, Duration, Stack, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda'; +import { RetentionDays } from 'aws-cdk-lib/aws-logs'; +import { v4 } from 'uuid'; +import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { Provider } from 'aws-cdk-lib/custom-resources'; +import { StringParameter } from 'aws-cdk-lib/aws-ssm'; +import path from 'path'; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; + +export interface CanaryStackProps extends StackProps { + readonly layerName: string; + readonly powertoolsPackageVersion: string; + readonly ssmParameterLayerArn: string; +} + +export class CanaryStack extends Stack { + public constructor(scope: Construct, id: string, props: CanaryStackProps) { + super(scope, id, props); + const { layerName, powertoolsPackageVersion } = props; + + const suffix = v4().substring(0, 5); + + const layerArn = StringParameter.fromStringParameterAttributes( + this, + 'LayerArn', + { + parameterName: props.ssmParameterLayerArn, + } + ).stringValue; + + // lambda function + const layer = [ + LayerVersion.fromLayerVersionArn(this, 'powertools-layer', layerArn), + ]; + + const canaryFunction = new NodejsFunction(this, 'CanaryFunction', { + entry: path.join( + __dirname, + '../tests/e2e/layerPublisher.class.test.functionCode.ts' + ), + handler: 'handler', + runtime: Runtime.NODEJS_18_X, + functionName: `canary-${suffix}`, + timeout: Duration.seconds(30), + bundling: { + externalModules: [ + // don't package these modules, we want to pull them from the layer + 'aws-sdk', + '@aws-lambda-powertools/logger', + '@aws-lambda-powertools/metrics', + '@aws-lambda-powertools/tracer', + '@aws-lambda-powertools/parameters', + '@aws-lambda-powertools/commons', + ], + }, + environment: { + POWERTOOLS_SERVICE_NAME: 'canary', + POWERTOOLS_PACKAGE_VERSION: powertoolsPackageVersion, + POWERTOOLS_LAYER_NAME: layerName, + SSM_PARAMETER_LAYER_ARN: props.ssmParameterLayerArn, + }, + layers: layer, + logRetention: RetentionDays.ONE_DAY, + }); + + canaryFunction.addToRolePolicy( + new PolicyStatement({ + actions: ['ssm:GetParameter'], + resources: ['*'], + effect: Effect.ALLOW, + }) + ); + + // use custom resource to trigger the lambda function during the CFN deployment + const provider = new Provider(this, 'CanaryCustomResourceProvider', { + onEventHandler: canaryFunction, + logRetention: RetentionDays.ONE_DAY, + }); + + // random suffix forces recreation of the custom resource otherwise the custom resource will be reused from prevous deployment + new CustomResource(this, `CanaryCustomResource${suffix}`, { + serviceToken: provider.serviceToken, + }); + } +} diff --git a/layers/tests/e2e/layerPublisher.class.test.functionCode.ts b/layers/tests/e2e/layerPublisher.class.test.functionCode.ts index 6606efffbc..28e60424e8 100644 --- a/layers/tests/e2e/layerPublisher.class.test.functionCode.ts +++ b/layers/tests/e2e/layerPublisher.class.test.functionCode.ts @@ -2,33 +2,31 @@ import { readFileSync } from 'node:fs'; import { Logger } from '@aws-lambda-powertools/logger'; import { Metrics } from '@aws-lambda-powertools/metrics'; import { Tracer } from '@aws-lambda-powertools/tracer'; +import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm'; const logger = new Logger({ logLevel: 'DEBUG', }); const metrics = new Metrics(); const tracer = new Tracer(); +new SSMProvider(); export const handler = (): void => { // Check that the packages version matches the expected one - try { - const packageJSON = JSON.parse( - readFileSync( - '/opt/nodejs/node_modules/@aws-lambda-powertools/logger/package.json', - { - encoding: 'utf8', - flag: 'r', - } - ) - ); + const packageJSON = JSON.parse( + readFileSync( + '/opt/nodejs/node_modules/@aws-lambda-powertools/logger/package.json', + { + encoding: 'utf8', + flag: 'r', + } + ) + ); - if (packageJSON.version != process.env.POWERTOOLS_PACKAGE_VERSION) { - throw new Error( - `Package version mismatch: ${packageJSON.version} != ${process.env.POWERTOOLS_PACKAGE_VERSION}` - ); - } - } catch (error) { - console.error(error); + if (packageJSON.version != process.env.POWERTOOLS_PACKAGE_VERSION) { + throw new Error( + `Package version mismatch: ${packageJSON.version} != ${process.env.POWERTOOLS_PACKAGE_VERSION}` + ); } // Check that the logger is working