diff --git a/README.md b/README.md index d77288f..58b533f 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,11 @@ custom: defaultLambdaInsights: true #enables Lambda Insights for all your functions, if attachPolicy: false #explicitly disable auto attachment Managed Policy. lambdaInsightsVersion: 14 #specify the Layer Version + lambdaInsightsAccount: '580247275435' #override the AWS account ID owning the Lambda Insights layer (see note below) ``` +**Note on `lambdaInsightsAccount`:** This setting only takes effect when `lambdaInsightsVersion` is explicitly set. It overrides the AWS account ID used to construct the Lambda Insights layer ARN. When no explicit version is set, the plugin resolves the layer ARN from its bundled mappings, which already contain the correct per-region accounts. You can find the per-region account IDs in the [AWS documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsx86-64.html). + ### Example You can find an example in the example folder of this repository. Run it with the following command. diff --git a/index.js b/index.js index b91ade8..b410ec6 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,10 @@ const layerVersionsArm64 = require('./layerVersionsArm64.json'); const lambdaInsightsManagedPolicy = 'arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy'; +// Default AWS account owning the Lambda Insights layer in most regions +// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versionsx86-64.html +const defaultLambdaInsightsAccount = '580247275435'; + /** * Serverless Lambda Insights Plugin - serverless-plugin-lambda-insights * @class AddLambdaInsights @@ -39,7 +43,8 @@ class AddLambdaInsights { defaultLambdaInsights: {type: 'boolean'}, attachPolicy: {type: 'boolean'}, lambdaInsightsVersion: {type: 'number'}, - } + lambdaInsightsAccount: {type: 'string'}, + }, }, }, }); @@ -87,24 +92,40 @@ class AddLambdaInsights { } } + /** + * Check if Lambda Insights Account is valid + * @param {any} value Value to check + * @return {string} return input value if a string + */ + checkLambdaInsightsAccount(value) { + if (typeof value === 'string') { + return value; + } else { + throw new Error('lambdaInsightsAccount must be a string.'); + } + } + /** * Generates a valid Lambda Insights Layer ARN for your Region * @param {number} version Value to check + * @param {string} architecture Function architecture + * @param {string} account AWS account ID for the layer * @return {string} Lambda Insights Layer ARN */ - async generateLayerARN(version, architecture) { + async generateLayerARN(version, architecture, account) { const region = this.provider.getRegion(); const isArm64 = architecture ? architecture === 'arm64' : this.service.provider.architecture === 'arm64'; if (version) { + const layerAccount = account || defaultLambdaInsightsAccount; try { let layerVersionInfo; if (isArm64) { layerVersionInfo = await this.provider.request('Lambda', 'getLayerVersionByArn', { - Arn: `arn:aws:lambda:${region}:580247275435:layer:LambdaInsightsExtension-Arm64:${version}`, + Arn: `arn:aws:lambda:${region}:${layerAccount}:layer:LambdaInsightsExtension-Arm64:${version}`, }); } else { layerVersionInfo = await this.provider.request('Lambda', 'getLayerVersionByArn', { - Arn: `arn:aws:lambda:${region}:580247275435:layer:LambdaInsightsExtension:${version}`, + Arn: `arn:aws:lambda:${region}:${layerAccount}:layer:LambdaInsightsExtension:${version}`, }); } return layerVersionInfo.LayerVersionArn; @@ -138,18 +159,18 @@ class AddLambdaInsights { * @param {boolean} globalLambdaInsights global settings * @param {number} layerVersion global layerVersion settings * @param {boolean} attachPolicy global attachPolicy settings + * @param {string} layerAccount AWS account ID for the Lambda Insights layer */ - async addLambdaInsightsToFunctions(globalLambdaInsights, layerVersion, attachPolicy) { + async addLambdaInsightsToFunctions(globalLambdaInsights, layerVersion, attachPolicy, layerAccount) { if (typeof this.service.functions !== 'object') { return; } try { - let policyToggle = false; const functions = Object.keys(this.service.functions); for (const functionName of functions) { const fn = this.service.functions[functionName]; - const layerARN = await this.generateLayerARN(layerVersion, fn.architecture); + const layerARN = await this.generateLayerARN(layerVersion, fn.architecture, layerAccount); const localLambdaInsights = fn.hasOwnProperty('lambdaInsights') ? this.checkLambdaInsightsType(fn.lambdaInsights) : null; @@ -211,7 +232,14 @@ class AddLambdaInsights { ) : null; - return this.addLambdaInsightsToFunctions(globalLambdaInsights, layerVersion, attachPolicy); + const layerAccount = + customLambdaInsights && customLambdaInsights.lambdaInsightsAccount ? + this.checkLambdaInsightsAccount( + customLambdaInsights.lambdaInsightsAccount, + ) : + null; + + return this.addLambdaInsightsToFunctions(globalLambdaInsights, layerVersion, attachPolicy, layerAccount); } } diff --git a/index.test.js b/index.test.js index 946a553..1c3606d 100644 --- a/index.test.js +++ b/index.test.js @@ -27,7 +27,8 @@ test('generateLayerArn defaults to global provider architecture to associates la .toStrictEqual(['arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension-Arm64:2']); }); -test('generateLayerArn local function architecture overwrites global setting to associates latest ARN for Arm64', async () => { +test('generateLayerArn local function architecture overwrites global setting' + + ' to associates latest ARN for Arm64', async () => { // arrange const serverless = createServerless('us-east-1'); serverless.service.functions.myFunction.architecture = 'arm64'; @@ -74,6 +75,19 @@ test('addLambdaInsights associates correct explicit layer version', async () => .toStrictEqual(['arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:12']); }); +test('addLambdaInsights uses custom account when explicit version is set', async () => { + // arrange + const serverless = createServerless('us-east-1', 12, '123456789012'); + const plugin = new AddLambdaInsights(serverless); + + // act + await plugin.addLambdaInsights(); + + // assert + expect(plugin.serverless.service.functions.myFunction.layers) + .toStrictEqual(['arn:aws:lambda:us-east-1:123456789012:layer:LambdaInsightsExtension:12']); +}); + test('addLambdaInsights throws for unknown region', async () => { // arrange const serverless = createServerless('not-a-region-1'); @@ -100,6 +114,19 @@ test('addLambdaInsights throws invalid lambdaInsightsVersion argument', async () .toThrow('lambdaInsightsVersion version must be a number.'); }); +test('addLambdaInsights throws invalid lambdaInsightsAccount argument', async () => { + // arrange + const serverless = createServerless('us-east-1', undefined, 12345); + const plugin = new AddLambdaInsights(serverless); + + // act + const task = () => plugin.addLambdaInsights(); + + // assert + await expect(task) + .toThrow('lambdaInsightsAccount must be a string.'); +}); + test('addLambdaInsights throws for invalid region version combination', async () => { // arrange const serverless = createServerless('us-east-1', 55555); @@ -122,18 +149,21 @@ test('addLambdaInsights adds IAM policy', async () => { // act await plugin.addLambdaInsights(); - // assert + // assert expect(plugin.service.provider.iamManagedPolicies) .toStrictEqual(['arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy']); }); -const createServerless = (region, LayerVersion) => { +const createServerless = (region, LayerVersion, LambdaInsightsAccount) => { const awsProvider = { getRegion: () => region, request: async (service, method, param) => { - // explicit layer version test is only valid for version 12 - if (param.Arn===`arn:aws:lambda:${region}:580247275435:layer:LambdaInsightsExtension:12`) { + // explicit layer version test is valid for version 12 with any account + const arnPattern = new RegExp( + `^arn:aws:lambda:${region}:\\d+:layer:LambdaInsightsExtension(-Arm64)?:12$`, + ); + if (arnPattern.test(param.Arn)) { return {LayerVersionArn: param.Arn}; } else { const customError = new Error(); @@ -142,6 +172,14 @@ const createServerless = (region, LayerVersion) => { } }, }; + + const lambdaInsightsConfig = { + lambdaInsightsVersion: LayerVersion, + }; + if (LambdaInsightsAccount !== undefined) { + lambdaInsightsConfig.lambdaInsightsAccount = LambdaInsightsAccount; + } + return { getProvider: () => awsProvider, configSchemaHandler: { @@ -155,9 +193,7 @@ const createServerless = (region, LayerVersion) => { architecture: 'x86_64', }, custom: { - lambdaInsights: { - lambdaInsightsVersion: LayerVersion, - }, + lambdaInsights: lambdaInsightsConfig, }, functions: { myFunction: {