-
-
Notifications
You must be signed in to change notification settings - Fork 48
Support for Node v14.x #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sodiray
wants to merge
15
commits into
lambci:master
Choose a base branch
from
sodiray:node-v14
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
13e6ab4
implement node v14.x boostrap
sodiray 86b1dd0
use const
sodiray 54e0a17
add top-level await to test file
sodiray 25e2133
bump node versoin env var to latest -> 14.15.1
sodiray 9b9289d
fix spelling typo
sodiray e1bbd40
clean up unneeded promise wrapper
sodiray 1e20df9
decrease test sleep time
sodiray 12ec96a
:tada: make v14 work with commonjs or module loader
sodiray 7b40e5e
skip creating require if using module loader
sodiray f7c9932
added custom loader hook to resolve /opt/nodejs/ modules
brandonryan fd8b0a9
Merge pull request #1 from brandonryan/node-v14
sodiray cc213ee
added more resolution paths and changed order or precedence
brandonryan 962955c
Merge pull request #2 from brandonryan/node-v14
sodiray 15d8dd3
removed loader type flag in favor of auto resolution
brandonryan 14a7da8
Merge pull request #3 from brandonryan/node-v14
sodiray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
layer.zip | ||
layer | ||
test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM lambci/lambda-base:build | ||
|
||
COPY bootstrap.c bootstrap.js package.json /opt/ | ||
|
||
ARG NODE_VERSION | ||
|
||
RUN curl -sSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | \ | ||
tar -xJ -C /opt --strip-components 1 -- node-v${NODE_VERSION}-linux-x64/bin/node && \ | ||
strip /opt/bin/node | ||
|
||
RUN cd /opt && \ | ||
export NODE_MAJOR=$(echo $NODE_VERSION | awk -F. '{print "\""$1"\""}') && \ | ||
clang -Wall -Werror -s -O2 -D NODE_MAJOR="$NODE_MAJOR" -o bootstrap bootstrap.c && \ | ||
rm bootstrap.c && \ | ||
zip -yr /tmp/layer.zip . |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <stdbool.h> | ||
#include <unistd.h> | ||
|
||
#ifndef NODE_MAJOR | ||
#error Must pass NODE_MAJOR to the compiler (eg "10") | ||
#define NODE_MAJOR "" | ||
#endif | ||
|
||
#define AWS_EXECUTION_ENV "AWS_Lambda_nodejs" NODE_MAJOR "_lambci" | ||
#define NODE_PATH "/opt/nodejs/node" NODE_MAJOR "/node_modules:" \ | ||
"/opt/nodejs/node_modules:" \ | ||
"/var/runtime/node_modules:" \ | ||
"/var/runtime:" \ | ||
"/var/task" | ||
#define MIN_MEM_SIZE 128 | ||
#define ARG_BUF_SIZE 32 | ||
|
||
int main(void) { | ||
setenv("AWS_EXECUTION_ENV", AWS_EXECUTION_ENV, true); | ||
setenv("NODE_PATH", NODE_PATH, true); | ||
|
||
const char *mem_size_str = getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE"); | ||
int mem_size = mem_size_str != NULL ? atoi(mem_size_str) : MIN_MEM_SIZE; | ||
|
||
char max_semi_space_size[ARG_BUF_SIZE]; | ||
snprintf(max_semi_space_size, ARG_BUF_SIZE, "--max-semi-space-size=%d", mem_size * 5 / 100); | ||
|
||
char max_old_space_size[ARG_BUF_SIZE]; | ||
snprintf(max_old_space_size, ARG_BUF_SIZE, "--max-old-space-size=%d", mem_size * 90 / 100); | ||
|
||
execv("/opt/bin/node", (char *[]){ | ||
"node", | ||
"--expose-gc", | ||
max_semi_space_size, | ||
max_old_space_size, | ||
"/opt/bootstrap.js", | ||
NULL}); | ||
perror("Could not execv"); | ||
return EXIT_FAILURE; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import http from 'http' | ||
|
||
const RUNTIME_PATH = '/2018-06-01/runtime' | ||
|
||
const CALLBACK_USED = Symbol('CALLBACK_USED') | ||
|
||
const { | ||
AWS_LAMBDA_FUNCTION_NAME, | ||
AWS_LAMBDA_FUNCTION_VERSION, | ||
AWS_LAMBDA_FUNCTION_MEMORY_SIZE, | ||
AWS_LAMBDA_LOG_GROUP_NAME, | ||
AWS_LAMBDA_LOG_STREAM_NAME, | ||
LAMBDA_TASK_ROOT, | ||
_HANDLER, | ||
AWS_LAMBDA_RUNTIME_API, | ||
} = process.env | ||
|
||
const [HOST, PORT] = AWS_LAMBDA_RUNTIME_API.split(':') | ||
|
||
start() | ||
|
||
async function start() { | ||
let handler | ||
try { | ||
handler = await getHandler() | ||
} catch (e) { | ||
await initError(e) | ||
return process.exit(1) | ||
} | ||
tryProcessEvents(handler) | ||
} | ||
|
||
async function tryProcessEvents(handler) { | ||
try { | ||
await processEvents(handler) | ||
} catch (e) { | ||
console.error(e) | ||
return process.exit(1) | ||
} | ||
} | ||
|
||
async function processEvents(handler) { | ||
while (true) { | ||
const { event, context } = await nextInvocation() | ||
|
||
let result | ||
try { | ||
result = await handler(event, context) | ||
} catch (e) { | ||
await invokeError(e, context) | ||
continue | ||
} | ||
const callbackUsed = context[CALLBACK_USED] | ||
|
||
await invokeResponse(result, context) | ||
|
||
if (callbackUsed && context.callbackWaitsForEmptyEventLoop) { | ||
return process.prependOnceListener('beforeExit', () => tryProcessEvents(handler)) | ||
} | ||
} | ||
} | ||
|
||
function initError(err) { | ||
return postError(`${RUNTIME_PATH}/init/error`, err) | ||
} | ||
|
||
async function nextInvocation() { | ||
const res = await request({ path: `${RUNTIME_PATH}/invocation/next` }) | ||
|
||
if (res.statusCode !== 200) { | ||
throw new Error(`Unexpected /invocation/next response: ${JSON.stringify(res)}`) | ||
} | ||
|
||
if (res.headers['lambda-runtime-trace-id']) { | ||
process.env._X_AMZN_TRACE_ID = res.headers['lambda-runtime-trace-id'] | ||
} else { | ||
delete process.env._X_AMZN_TRACE_ID | ||
} | ||
|
||
const deadlineMs = +res.headers['lambda-runtime-deadline-ms'] | ||
|
||
const context = { | ||
awsRequestId: res.headers['lambda-runtime-aws-request-id'], | ||
invokedFunctionArn: res.headers['lambda-runtime-invoked-function-arn'], | ||
logGroupName: AWS_LAMBDA_LOG_GROUP_NAME, | ||
logStreamName: AWS_LAMBDA_LOG_STREAM_NAME, | ||
functionName: AWS_LAMBDA_FUNCTION_NAME, | ||
functionVersion: AWS_LAMBDA_FUNCTION_VERSION, | ||
memoryLimitInMB: AWS_LAMBDA_FUNCTION_MEMORY_SIZE, | ||
getRemainingTimeInMillis: () => deadlineMs - Date.now(), | ||
callbackWaitsForEmptyEventLoop: true, | ||
} | ||
|
||
if (res.headers['lambda-runtime-client-context']) { | ||
context.clientContext = JSON.parse(res.headers['lambda-runtime-client-context']) | ||
} | ||
|
||
if (res.headers['lambda-runtime-cognito-identity']) { | ||
context.identity = JSON.parse(res.headers['lambda-runtime-cognito-identity']) | ||
} | ||
|
||
const event = JSON.parse(res.body) | ||
|
||
return { event, context } | ||
} | ||
|
||
async function invokeResponse(result, context) { | ||
const res = await request({ | ||
method: 'POST', | ||
path: `${RUNTIME_PATH}/invocation/${context.awsRequestId}/response`, | ||
body: JSON.stringify(result === undefined ? null : result), | ||
}) | ||
if (res.statusCode !== 202) { | ||
throw new Error(`Unexpected /invocation/response response: ${JSON.stringify(res)}`) | ||
} | ||
} | ||
|
||
function invokeError(err, context) { | ||
return postError(`${RUNTIME_PATH}/invocation/${context.awsRequestId}/error`, err) | ||
} | ||
|
||
async function postError(path, err) { | ||
const lambdaErr = toLambdaErr(err) | ||
const res = await request({ | ||
method: 'POST', | ||
path, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Lambda-Runtime-Function-Error-Type': lambdaErr.errorType, | ||
}, | ||
body: JSON.stringify(lambdaErr), | ||
}) | ||
if (res.statusCode !== 202) { | ||
throw new Error(`Unexpected ${path} response: ${JSON.stringify(res)}`) | ||
} | ||
} | ||
|
||
async function getHandler() { | ||
const appParts = _HANDLER.split('.') | ||
|
||
if (appParts.length !== 2) { | ||
throw new Error(`Bad handler ${_HANDLER}`) | ||
} | ||
|
||
const [modulePath, handlerName] = appParts | ||
|
||
// Let any errors here be thrown as-is to aid debugging | ||
const importPath = `${LAMBDA_TASK_ROOT}/${modulePath}.js` | ||
const app = await new Promise((res, rej) => import(importPath).then(res).catch(rej)) | ||
|
||
const userHandler = app[handlerName] | ||
|
||
if (userHandler == null) { | ||
throw new Error(`Handler '${handlerName}' missing on module '${modulePath}'`) | ||
} else if (typeof userHandler !== 'function') { | ||
throw new Error(`Handler '${handlerName}' from '${modulePath}' is not a function`) | ||
} | ||
|
||
return (event, context) => new Promise((resolve, reject) => { | ||
context.succeed = resolve | ||
context.fail = reject | ||
context.done = (err, data) => err ? reject(err) : resolve(data) | ||
|
||
const callback = (err, data) => { | ||
context[CALLBACK_USED] = true | ||
context.done(err, data) | ||
} | ||
|
||
let result | ||
try { | ||
result = userHandler(event, context, callback) | ||
} catch (e) { | ||
return reject(e) | ||
} | ||
if (result != null && typeof result.then === 'function') { | ||
result.then(resolve, reject) | ||
} | ||
}) | ||
} | ||
|
||
function request(options) { | ||
options.host = HOST | ||
options.port = PORT | ||
|
||
return new Promise((resolve, reject) => { | ||
const req = http.request(options, res => { | ||
const bufs = [] | ||
res.on('data', data => bufs.push(data)) | ||
res.on('end', () => resolve({ | ||
statusCode: res.statusCode, | ||
headers: res.headers, | ||
body: Buffer.concat(bufs).toString(), | ||
})) | ||
res.on('error', reject) | ||
}) | ||
req.on('error', reject) | ||
req.end(options.body) | ||
}) | ||
} | ||
|
||
function toLambdaErr(err) { | ||
const { name, message, stack } = err | ||
return { | ||
errorType: name || typeof err, | ||
errorMessage: message || ('' + err), | ||
stackTrace: (stack || '').split('\n').slice(1), | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/sh | ||
|
||
. ./config.sh | ||
|
||
docker build --build-arg NODE_VERSION -t node-provided-lambda-v14.x . | ||
docker run --rm -v "$PWD":/app node-provided-lambda-v14.x cp /tmp/layer.zip /app/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/bin/bash | ||
|
||
. ./config.sh | ||
|
||
REGIONS="$(aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/services/lambda/regions \ | ||
--query 'Parameters[].Value' --output text | tr '[:blank:]' '\n' | grep -v -e ^cn- -e ^us-gov- | sort -r)" | ||
|
||
for region in $REGIONS; do | ||
aws lambda list-layer-versions --region $region --layer-name $LAYER_NAME \ | ||
--query 'LayerVersions[*].[LayerVersionArn]' --output text | ||
done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export LAYER_NAME=nodejs14 | ||
export NODE_VERSION=14.3.0 | ||
sodiray marked this conversation as resolved.
Show resolved
Hide resolved
|
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "node-custom-lambda-v14.x", | ||
"version": "1.0.0", | ||
"type": "module" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/bin/bash | ||
|
||
. ./config.sh | ||
|
||
DESCRIPTION="Node.js v${NODE_VERSION} custom runtime" | ||
FILENAME=${LAYER_NAME}-${NODE_VERSION}.zip | ||
|
||
REGIONS="$(aws ssm get-parameters-by-path --path /aws/service/global-infrastructure/services/lambda/regions \ | ||
--query 'Parameters[].Value' --output text | tr '[:blank:]' '\n' | grep -v -e ^cn- -e ^us-gov- -e ^ap-northeast-3 | sort -r)" | ||
|
||
aws s3api put-object --bucket lambci --key layers/${FILENAME} --body layer.zip | ||
|
||
for region in $REGIONS; do | ||
aws s3api copy-object --region $region --copy-source lambci/layers/${FILENAME} \ | ||
--bucket lambci-${region} --key layers/${FILENAME} && \ | ||
aws lambda add-layer-version-permission --region $region --layer-name $LAYER_NAME \ | ||
--statement-id sid1 --action lambda:GetLayerVersion --principal '*' \ | ||
--version-number $(aws lambda publish-layer-version --region $region --layer-name $LAYER_NAME \ | ||
--content S3Bucket=lambci-${region},S3Key=layers/${FILENAME} \ | ||
--description "$DESCRIPTION" --query Version --output text) & | ||
done | ||
|
||
for job in $(jobs -p); do | ||
wait $job | ||
done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/bin/sh | ||
|
||
rm -rf layer && unzip layer.zip -d layer | ||
|
||
cd test | ||
|
||
npm ci | ||
|
||
# Create zipfile for uploading to Lambda – we don't use this here | ||
rm -f lambda.zip && zip -qyr lambda.zip index.js node_modules | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler2 | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler3 | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler4 | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler5 | ||
|
||
docker run --rm -v "$PWD":/var/task -v "$PWD"/../layer:/opt lambci/lambda:provided index.handler6 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Test that global requires work | ||
import aws4 from 'aws4' | ||
|
||
const interval = setInterval(console.log, 100, 'ping') | ||
|
||
const sleep = async (miliseconds) => await new Promise(res => setTimeout(res, miliseconds)) | ||
sodiray marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Test top-level await works | ||
await sleep(1000) | ||
|
||
export const handler = async (event, context) => { | ||
console.log(process.version) | ||
console.log(process.execPath) | ||
console.log(process.execArgv) | ||
console.log(process.argv) | ||
console.log(process.cwd()) | ||
console.log(process.env) | ||
console.log(event) | ||
console.log(context) | ||
console.log(context.getRemainingTimeInMillis()) | ||
console.log(aws4) | ||
return { some: 'obj!' } | ||
} | ||
|
||
export const handler2 = (event, context) => { | ||
setTimeout(context.done, 100, null, { some: 'obj!' }) | ||
} | ||
|
||
export const handler3 = (event, context) => { | ||
setTimeout(context.succeed, 100, { some: 'obj!' }) | ||
} | ||
|
||
export const handler4 = (event, context) => { | ||
setTimeout(context.fail, 100, new Error('This error should be logged')) | ||
} | ||
|
||
export const handler5 = (event, context, cb) => { | ||
setTimeout(cb, 100, null, { some: 'obj!' }) | ||
setTimeout(clearInterval, 100, interval) | ||
} | ||
|
||
export const handler6 = (event, context, cb) => { | ||
context.callbackWaitsForEmptyEventLoop = false | ||
setTimeout(cb, 100, null, { some: 'obj!' }) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.