Skip to content

Commit

Permalink
fix(aws-lambda): patch aws-lambda to wait for lambda to be in ready s…
Browse files Browse the repository at this point in the history
…tate
  • Loading branch information
dphang committed Nov 28, 2021
1 parent a398cdb commit 3ae7d7e
Show file tree
Hide file tree
Showing 7 changed files with 1,939 additions and 1,650 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"packages-build": "lerna run build",
"test:watch": "yarn test --watch --collect-coverage=false",
"check-gh-token": ": \"${GH_TOKEN:?Please set GH_TOKEN to a GitHub personal token that can create releases.}\"",
"patch": "yarn check-gh-token && lerna publish --conventional-commits --conventional-prerelease --exact --create-release github --dist-tag patch --preid patch",
"publish": "yarn check-gh-token && lerna publish --conventional-commits --exact --create-release github",
"prerelease": "yarn check-gh-token && lerna publish --conventional-commits --conventional-prerelease --exact --create-release github --dist-tag alpha",
"graduate": "yarn check-gh-token && lerna publish --conventional-commits --conventional-graduate --exact --create-release github",
Expand All @@ -39,6 +40,7 @@
},
"homepage": "https://github.com/serverless-nextjs/serverless-next.js#readme",
"devDependencies": {
"@babel/helper-compilation-targets": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
"@sls-next/lambda-at-edge": "link:./packages/libs/lambda-at-edge",
"@sls-next/next-aws-cloudfront": "link:./packages/compat-layers/lambda-at-edge-compat",
Expand All @@ -58,7 +60,7 @@
"husky": "^4.2.5",
"jest": "^26.1.0",
"jest-when": "^2.7.2",
"lerna": "^3.22.1",
"lerna": "^4.0.0",
"lint-staged": "^10.2.11",
"next": "^9.4.4",
"prettier": "^2.0.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const mockCreateFunctionPromise = promisifyMock(mockCreateFunction);
const mockPublishVersion = jest.fn();
const mockPublishVersionPromise = promisifyMock(mockPublishVersion);

const mockGetFunction = jest.fn();
const mockGetFunctionPromise = promisifyMock(mockGetFunction);

const mockGetFunctionConfiguration = jest.fn();
const mockGetFunctionConfigurationPromise = promisifyMock(
mockGetFunctionConfiguration
Expand All @@ -37,12 +40,15 @@ module.exports = {
mockUpdateFunctionCodePromise,
mockUpdateFunctionConfiguration,
mockUpdateFunctionConfigurationPromise,
mockGetFunction,
mockGetFunctionPromise,

Lambda: jest.fn(() => ({
createFunction: mockCreateFunction,
publishVersion: mockPublishVersion,
getFunctionConfiguration: mockGetFunctionConfiguration,
updateFunctionCode: mockUpdateFunctionCode,
updateFunctionConfiguration: mockUpdateFunctionConfiguration
updateFunctionConfiguration: mockUpdateFunctionConfiguration,
getFunction: mockGetFunction
}))
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const {
mockPublishVersionPromise,
mockGetFunctionConfigurationPromise,
mockUpdateFunctionCodePromise,
mockUpdateFunctionConfigurationPromise
mockUpdateFunctionConfigurationPromise,
mockGetFunctionPromise
} = require("aws-sdk");

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

mockGetFunctionPromise.mockResolvedValue({
Configuration: {
State: "Active",
LastUpdateStatus: "Successful"
}
});

component = await createComponent();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const {
mockGetFunctionPromise,
mockGetFunction
} = require("../__mocks__/aws-sdk.mock");
const { waitUntilReady } = require("../waitUntilReady");

jest.mock("aws-sdk", () => require("../__mocks__/aws-sdk.mock"));

describe("waitLambdaReady", () => {
it("waits until lambda is ready", async () => {
mockGetFunctionPromise.mockResolvedValueOnce({
Configuration: {
State: "Pending",
LastUpdateStatus: "InProgress"
}
});

mockGetFunctionPromise.mockResolvedValueOnce({
Configuration: {
State: "Active",
LastUpdateStatus: "Successful"
}
});

const ready = await waitUntilReady(
{
debug: () => {
// intentionally empty
}
},
"test-function",
"us-east-1",
1
);

expect(ready).toBe(true);

expect(mockGetFunction).toBeCalledWith({
FunctionName: "test-function"
});
expect(mockGetFunction).toBeCalledTimes(2); // since first time it's mocked as not ready
});
});
10 changes: 10 additions & 0 deletions packages/serverless-components/aws-lambda/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const aws = require("aws-sdk");
const AwsSdkLambda = aws.Lambda;
const { mergeDeepRight, pick } = require("ramda");
const { Component, utils } = require("@serverless/core");
const { waitUntilReady } = require("./waitUntilReady");
const {
createLambda,
updateLambdaCode,
Expand Down Expand Up @@ -82,6 +83,9 @@ class AwsLambda extends Component {
const createResult = await createLambda({ lambda, ...config });
config.arn = createResult.arn;
config.hash = createResult.hash;

// Wait for Lambda to be in a ready state after creation and before doing anything else
await waitUntilReady(this.context, config.name, config.region);
} else {
config.arn = prevLambda.arn;

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

// Wait for Lambda to be in a ready state after code updated and before doing anything else
await waitUntilReady(this.context, config.name, config.region);

this.context.status(`Updating`);
this.context.debug(`Updating ${config.name} lambda config.`);

const updateResult = await updateLambdaConfig({ lambda, ...config });
config.hash = updateResult.hash;

// Wait for Lambda to be ready after updating config and before doing anything else
await waitUntilReady(this.context, config.name, config.region);
}
}

Expand Down
31 changes: 31 additions & 0 deletions packages/serverless-components/aws-lambda/waitUntilReady.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const AWS = require("aws-sdk");

/**
* Wait up to 10 minutes for the Lambda to be ready.
* This is needed due to: https://docs.aws.amazon.com/lambda/latest/dg/functions-states.html
*/
const waitUntilReady = async (context, fnName, region, pollInterval = 5000) => {
const lambda = new AWS.Lambda({ region });
const startDate = new Date();
const startTime = startDate.getTime();
const waitDurationMillis = 600000; // 10 minutes max wait time

context.debug(`Waiting up to 600 seconds for Lambda ${fnName} to be ready.`);

while (new Date().getTime() - startTime < waitDurationMillis) {
const {
Configuration: { LastUpdateStatus, State }
} = await lambda.getFunction({ FunctionName: fnName }).promise();

if (State === "Active" && LastUpdateStatus === "Successful") {
return true;
}
await new Promise((r) => setTimeout(r, pollInterval)); // retry every 5 seconds
}

return false;
};

module.exports = {
waitUntilReady
};

0 comments on commit 3ae7d7e

Please sign in to comment.