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

Commit a0a22ab

Browse files
authored
fix(aws-lambda, nextjs-component): ensure we wait until lambda functions are ready before using them (#1982)
1 parent ec19109 commit a0a22ab

File tree

7 files changed

+101
-2
lines changed

7 files changed

+101
-2
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export const mockCreateFunctionPromise = promisifyMock(mockCreateFunction);
1515
export const mockPublishVersion = jest.fn();
1616
export const mockPublishVersionPromise = promisifyMock(mockPublishVersion);
1717

18+
export const mockGetFunction = jest.fn();
19+
export const mockGetFunctionPromise = promisifyMock(mockGetFunction);
20+
1821
export const mockGetFunctionConfiguration = jest.fn();
1922
export const mockGetFunctionConfigurationPromise = promisifyMock(
2023
mockGetFunctionConfiguration
@@ -87,6 +90,7 @@ export default {
8790
tagResource: mockTagResource,
8891
untagResource: mockUntagResource,
8992
listVersionsByFunction: mockListVersionsByFunction,
90-
deleteFunction: mockDeleteFunction
93+
deleteFunction: mockDeleteFunction,
94+
getFunction: mockGetFunction
9195
}))
9296
};

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { createComponent, createTmpDir } from "../test-utils";
33
import {
44
mockCreateFunction,
55
mockCreateFunctionPromise,
6+
mockGetFunction,
7+
mockGetFunctionPromise,
68
mockPublishVersion,
79
mockPublishVersionPromise,
810
mockGetFunctionConfigurationPromise,
@@ -44,6 +46,12 @@ describe("publishVersion", () => {
4446
FunctionArn: "arn:aws:lambda:us-east-1:123456789012:function:my-func",
4547
CodeSha256: "LQT0VA="
4648
});
49+
mockGetFunctionPromise.mockResolvedValue({
50+
Configuration: {
51+
State: "Active",
52+
LastUpdateStatus: "Successful"
53+
}
54+
});
4755

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

packages/serverless-components/aws-lambda/src/component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
configChanged,
1212
pack
1313
} from "./utils";
14+
import { waitUntilReady } from "./waitUntilReady";
1415

1516
const outputsList = [
1617
"name",
@@ -121,6 +122,9 @@ class AwsLambda extends Component {
121122
await deleteLambda({ lambda, name: this.state.name });
122123
}
123124

125+
// Wait for Lambda to be in a ready state
126+
await waitUntilReady(this.context, config.name, config.region);
127+
124128
this.context.debug(
125129
`Successfully deployed lambda ${config.name} in the ${config.region} region.`
126130
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import AWS from "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+
export const waitUntilReady = async (
8+
context: any,
9+
fnName: string,
10+
region: string,
11+
pollInterval = 5000
12+
): Promise<boolean> => {
13+
const lambda: AWS.Lambda = new AWS.Lambda({ region });
14+
const startDate = new Date();
15+
const startTime = startDate.getTime();
16+
const waitDurationMillis = 600000; // 10 minutes max wait time
17+
18+
context.debug(`Waiting up to 600 seconds for Lambda ${fnName} to be ready.`);
19+
20+
while (new Date().getTime() - startTime < waitDurationMillis) {
21+
const {
22+
Configuration: { LastUpdateStatus, State }
23+
} = await lambda.getFunction({ FunctionName: fnName }).promise();
24+
25+
if (State === "Active" && LastUpdateStatus === "Successful") {
26+
return true;
27+
}
28+
await new Promise((r) => setTimeout(r, pollInterval)); // retry every 5 seconds
29+
}
30+
31+
return false;
32+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { jest } from "@jest/globals";
2+
3+
const mockWaitUtilReady = jest.fn();
4+
5+
module.exports = {
6+
mockWaitUtilReady,
7+
waitUntilReady: mockWaitUtilReady
8+
};

packages/serverless-components/nextjs-component/src/component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import type {
3333
import { execSync } from "child_process";
3434
import AWS from "aws-sdk";
3535
import { removeLambdaVersions } from "@sls-next/aws-lambda/dist/removeLambdaVersions";
36-
3736
// Message when deployment is explicitly skipped
3837
const SKIPPED_DEPLOY = "SKIPPED_DEPLOY";
3938

0 commit comments

Comments
 (0)