Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 36 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -39,7 +43,8 @@ class AddLambdaInsights {
defaultLambdaInsights: {type: 'boolean'},
attachPolicy: {type: 'boolean'},
lambdaInsightsVersion: {type: 'number'},
}
lambdaInsightsAccount: {type: 'string'},
},
},
},
});
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
52 changes: 44 additions & 8 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -142,6 +172,14 @@ const createServerless = (region, LayerVersion) => {
}
},
};

const lambdaInsightsConfig = {
lambdaInsightsVersion: LayerVersion,
};
if (LambdaInsightsAccount !== undefined) {
lambdaInsightsConfig.lambdaInsightsAccount = LambdaInsightsAccount;
}

return {
getProvider: () => awsProvider,
configSchemaHandler: {
Expand All @@ -155,9 +193,7 @@ const createServerless = (region, LayerVersion) => {
architecture: 'x86_64',
},
custom: {
lambdaInsights: {
lambdaInsightsVersion: LayerVersion,
},
lambdaInsights: lambdaInsightsConfig,
},
functions: {
myFunction: {
Expand Down