Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 3ae7d7e

Browse files
committed
fix(aws-lambda): patch aws-lambda to wait for lambda to be in ready state
1 parent a398cdb commit 3ae7d7e

File tree

7 files changed

+1939
-1650
lines changed

7 files changed

+1939
-1650
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"packages-build": "lerna run build",
2020
"test:watch": "yarn test --watch --collect-coverage=false",
2121
"check-gh-token": ": \"${GH_TOKEN:?Please set GH_TOKEN to a GitHub personal token that can create releases.}\"",
22+
"patch": "yarn check-gh-token && lerna publish --conventional-commits --conventional-prerelease --exact --create-release github --dist-tag patch --preid patch",
2223
"publish": "yarn check-gh-token && lerna publish --conventional-commits --exact --create-release github",
2324
"prerelease": "yarn check-gh-token && lerna publish --conventional-commits --conventional-prerelease --exact --create-release github --dist-tag alpha",
2425
"graduate": "yarn check-gh-token && lerna publish --conventional-commits --conventional-graduate --exact --create-release github",
@@ -39,6 +40,7 @@
3940
},
4041
"homepage": "https://github.com/serverless-nextjs/serverless-next.js#readme",
4142
"devDependencies": {
43+
"@babel/helper-compilation-targets": "^7.10.4",
4244
"@babel/preset-typescript": "^7.10.4",
4345
"@sls-next/lambda-at-edge": "link:./packages/libs/lambda-at-edge",
4446
"@sls-next/next-aws-cloudfront": "link:./packages/compat-layers/lambda-at-edge-compat",
@@ -58,7 +60,7 @@
5860
"husky": "^4.2.5",
5961
"jest": "^26.1.0",
6062
"jest-when": "^2.7.2",
61-
"lerna": "^3.22.1",
63+
"lerna": "^4.0.0",
6264
"lint-staged": "^10.2.11",
6365
"next": "^9.4.4",
6466
"prettier": "^2.0.5",

packages/serverless-components/aws-lambda/__mocks__/aws-sdk.mock.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const mockCreateFunctionPromise = promisifyMock(mockCreateFunction);
1313
const mockPublishVersion = jest.fn();
1414
const mockPublishVersionPromise = promisifyMock(mockPublishVersion);
1515

16+
const mockGetFunction = jest.fn();
17+
const mockGetFunctionPromise = promisifyMock(mockGetFunction);
18+
1619
const mockGetFunctionConfiguration = jest.fn();
1720
const mockGetFunctionConfigurationPromise = promisifyMock(
1821
mockGetFunctionConfiguration
@@ -37,12 +40,15 @@ module.exports = {
3740
mockUpdateFunctionCodePromise,
3841
mockUpdateFunctionConfiguration,
3942
mockUpdateFunctionConfigurationPromise,
43+
mockGetFunction,
44+
mockGetFunctionPromise,
4045

4146
Lambda: jest.fn(() => ({
4247
createFunction: mockCreateFunction,
4348
publishVersion: mockPublishVersion,
4449
getFunctionConfiguration: mockGetFunctionConfiguration,
4550
updateFunctionCode: mockUpdateFunctionCode,
46-
updateFunctionConfiguration: mockUpdateFunctionConfiguration
51+
updateFunctionConfiguration: mockUpdateFunctionConfiguration,
52+
getFunction: mockGetFunction
4753
}))
4854
};

packages/serverless-components/aws-lambda/__tests__/publishVersion.test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const {
66
mockPublishVersionPromise,
77
mockGetFunctionConfigurationPromise,
88
mockUpdateFunctionCodePromise,
9-
mockUpdateFunctionConfigurationPromise
9+
mockUpdateFunctionConfigurationPromise,
10+
mockGetFunctionPromise
1011
} = require("aws-sdk");
1112

1213
jest.mock("aws-sdk", () => require("../__mocks__/aws-sdk.mock"));
@@ -34,6 +35,13 @@ describe("publishVersion", () => {
3435
CodeSha256: "LQT0VA="
3536
});
3637

38+
mockGetFunctionPromise.mockResolvedValue({
39+
Configuration: {
40+
State: "Active",
41+
LastUpdateStatus: "Successful"
42+
}
43+
});
44+
3745
component = await createComponent();
3846
});
3947

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const {
2+
mockGetFunctionPromise,
3+
mockGetFunction
4+
} = require("../__mocks__/aws-sdk.mock");
5+
const { waitUntilReady } = require("../waitUntilReady");
6+
7+
jest.mock("aws-sdk", () => require("../__mocks__/aws-sdk.mock"));
8+
9+
describe("waitLambdaReady", () => {
10+
it("waits until lambda is ready", async () => {
11+
mockGetFunctionPromise.mockResolvedValueOnce({
12+
Configuration: {
13+
State: "Pending",
14+
LastUpdateStatus: "InProgress"
15+
}
16+
});
17+
18+
mockGetFunctionPromise.mockResolvedValueOnce({
19+
Configuration: {
20+
State: "Active",
21+
LastUpdateStatus: "Successful"
22+
}
23+
});
24+
25+
const ready = await waitUntilReady(
26+
{
27+
debug: () => {
28+
// intentionally empty
29+
}
30+
},
31+
"test-function",
32+
"us-east-1",
33+
1
34+
);
35+
36+
expect(ready).toBe(true);
37+
38+
expect(mockGetFunction).toBeCalledWith({
39+
FunctionName: "test-function"
40+
});
41+
expect(mockGetFunction).toBeCalledTimes(2); // since first time it's mocked as not ready
42+
});
43+
});

packages/serverless-components/aws-lambda/serverless.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const aws = require("aws-sdk");
22
const AwsSdkLambda = aws.Lambda;
33
const { mergeDeepRight, pick } = require("ramda");
44
const { Component, utils } = require("@serverless/core");
5+
const { waitUntilReady } = require("./waitUntilReady");
56
const {
67
createLambda,
78
updateLambdaCode,
@@ -82,6 +83,9 @@ class AwsLambda extends Component {
8283
const createResult = await createLambda({ lambda, ...config });
8384
config.arn = createResult.arn;
8485
config.hash = createResult.hash;
86+
87+
// Wait for Lambda to be in a ready state after creation and before doing anything else
88+
await waitUntilReady(this.context, config.name, config.region);
8589
} else {
8690
config.arn = prevLambda.arn;
8791

@@ -92,11 +96,17 @@ class AwsLambda extends Component {
9296
await updateLambdaCode({ lambda, ...config });
9397
}
9498

99+
// Wait for Lambda to be in a ready state after code updated and before doing anything else
100+
await waitUntilReady(this.context, config.name, config.region);
101+
95102
this.context.status(`Updating`);
96103
this.context.debug(`Updating ${config.name} lambda config.`);
97104

98105
const updateResult = await updateLambdaConfig({ lambda, ...config });
99106
config.hash = updateResult.hash;
107+
108+
// Wait for Lambda to be ready after updating config and before doing anything else
109+
await waitUntilReady(this.context, config.name, config.region);
100110
}
101111
}
102112

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const AWS = require("aws-sdk");
2+
3+
/**
4+
* Wait up to 10 minutes for the Lambda to be ready.
5+
* This is needed due to: https://docs.aws.amazon.com/lambda/latest/dg/functions-states.html
6+
*/
7+
const waitUntilReady = async (context, fnName, region, pollInterval = 5000) => {
8+
const lambda = new AWS.Lambda({ region });
9+
const startDate = new Date();
10+
const startTime = startDate.getTime();
11+
const waitDurationMillis = 600000; // 10 minutes max wait time
12+
13+
context.debug(`Waiting up to 600 seconds for Lambda ${fnName} to be ready.`);
14+
15+
while (new Date().getTime() - startTime < waitDurationMillis) {
16+
const {
17+
Configuration: { LastUpdateStatus, State }
18+
} = await lambda.getFunction({ FunctionName: fnName }).promise();
19+
20+
if (State === "Active" && LastUpdateStatus === "Successful") {
21+
return true;
22+
}
23+
await new Promise((r) => setTimeout(r, pollInterval)); // retry every 5 seconds
24+
}
25+
26+
return false;
27+
};
28+
29+
module.exports = {
30+
waitUntilReady
31+
};

0 commit comments

Comments
 (0)