#!/bin/shset -euo pipefail
SCRIPT_DIR=$(cd $(dirname $0); pwd)HANDLER_NAME=$(echo "$_HANDLER" | cut -d. -f2)HANDLER_FILE=$(echo "$_HANDLER" | cut -d. -f1)
# support deno in the /bin directory of the function (not only the layer)PATH=$SCRIPT_DIR/bin:$PATH
API_ROOT=http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/
# If this ENV variable is set then copy that directory as DENO_DIR (default = .deno_dir).# Note: For permissions reasons we need the actual DENO_DIR to be somewhere in /tmp.DENO_DIR=${DENO_DIR-.deno_dir}if [[ -z "$DENO_DIR" ]]; then DENO_DIR=.deno_dirfi
# Potentially this could be overwritten HOWEVER several permissions are required for bootstrap.# Note: Do can plugins work?? (If so, it should be documented.)DENO_FLAGS="--no-check"DENO_CACHE_FLAGS=""
DENO_PERMISSIONS=${DENO_PERMISSIONS--A}DENO_FLAGS="$DENO_PERMISSIONS $DENO_FLAGS"
# For unstable flags we must pass --unstableDENO_UNSTABLE=${DENO_UNSTABLE-}if [[ ! -z "$DENO_UNSTABLE" ]]; then DENO_FLAGS="$DENO_FLAGS --unstable" DENO_CACHE_FLAGS="$DENO_CACHE_FLAGS --unstable"fi
# Once there is a convention for lock filename we could look for its existence.# For now we require it to be passed as an env explicitly.DENO_LOCK=${DENO_LOCK-}if [[ ! -z "$DENO_LOCK" ]]; then DENO_FLAGS="$DENO_FLAGS --lock=$DENO_LOCK" DENO_CACHE_FLAGS="$DENO_CACHE_FLAGS --lock=$DENO_LOCK"fi
DENO_CONFIG=${DENO_CONFIG-}if [[ ! -z "$DENO_CONFIG" ]]; then DENO_FLAGS="$DENO_FLAGS --config=$DENO_CONFIG" DENO_CACHE_FLAGS="$DENO_CACHE_FLAGS --config=$DENO_CONFIG"fi
DENO_IMPORTMAP=${DENO_IMPORTMAP-}if [[ ! -z "$DENO_IMPORTMAP" ]]; then DENO_FLAGS="$DENO_FLAGS --importmap=$DENO_IMPORTMAP" DENO_CACHE_FLAGS="$DENO_CACHE_FLAGS --importmap=$DENO_IMPORTMAP"fi
DENO_LOCATION=${DENO_LOCATION-}if [[ ! -z "$DENO_LOCATION" ]]; then DENO_FLAGS="$DENO_FLAGS --location=$DENO_LOCATION" DENO_CACHE_FLAGS="$DENO_CACHE_FLAGS --location=$DENO_LOCATION"fi
DENO_PREFIX=${DENO_PREFIX-"\${level}\tRequestId: \${requestId}\r"}
# whether to use ts, js, bundle.js, etc.HANDLER_EXT=${HANDLER_EXT-'ts'}HANDLER_FILE=${HANDLER_FILE}.${HANDLER_EXT}
# If we fail prior to the handler loop we post an init error.function error { echo "error:" $1 ERROR="{\"errorMessage\" : \"$1\", \"errorType\" : \"InitException\"}" curl -s -X POST "${API_ROOT}init/error" \ -d "$ERROR" \ --header "Lambda-Runtime-Function-Error-Type: Unhandled" \ --output /tmp/init.out # expect it to be {"status":"OK"} grep -q OK /tmp/init.out \ || echo "Unexpected bootstrap error when calling AWS_LAMBDA_RUNTIME_API /init/error:" $(cat /tmp/init.out) exit 1}
# If the script fails we try and determine why.function investigate { [ -f $SCRIPT_DIR/bin/deno ] \ || error "missing deno executable" type -P deno &> /dev/null \ || error "deno executable not found in PATH" DENO_DIR=/tmp/deno_dir NO_COLOR=true DENO_NO_UPDATE_CHECK=true deno eval 'Deno.version.deno' \ || error "bad deno executable" [ -f $LAMBDA_TASK_ROOT/$HANDLER_FILE ] \ || error "missing expected handler file '$HANDLER_FILE'" if [[ ! -z "$DENO_LOCK" ]]; then [ -f $DENO_LOCK ] \ || error "missing lock file '$DENO_LOCK'" # This second check might be unecessary (in that it would be caught by the 'unable to compile') DENO_DIR=/tmp/deno_dir NO_COLOR=true DENO_NO_UPDATE_CHECK=true deno cache $DENO_CACHE_FLAGS $LAMBDA_TASK_ROOT/$HANDLER_FILE &> /tmp/lock.out \ || error "lock file error: $(cat /tmp/lock.out)" fi DENO_DIR=/tmp/deno_dir NO_COLOR=true DENO_NO_UPDATE_CHECK=true deno cache $DENO_CACHE_FLAGS $LAMBDA_TASK_ROOT/$HANDLER_FILE &> /dev/null \ || error "unable to compile $HANDLER_FILE" # e.g. the HANDLER_FILE threw an error or did not import HANDLER_NAME error "deno exited"}
# Note: This is a js file to avoid a runtime compilation step.# Hopefully $HANDLER_FILE's compilation is cached in DENO_DIR.echo "import { $HANDLER_NAME as handle } from '$LAMBDA_TASK_ROOT/$HANDLER_FILE';const INVOCATION = '${API_ROOT}invocation/';
function maybeJson(headers, headerName) { const json = headers.get(headerName); if (json) { try { return JSON.parse(json); } catch (e) { console.error( 'Unable to parse header', headerName, 'value as JSON:', json ); } }}
const prefix = Deno.env.get('DENO_PREFIX')let interpolate = prefix ? (params) => { const names = Object.keys(params); const vals = Object.values(params); return new Function(...names, \`return \\\`\${prefix}\\\`;\`)(...vals);} : (x) => { return '' };
try { interpolate({ requestId: 'a', level: 'TEST' });} catch (e) { console.log('warn: DENO_PREFIX', e.message); interpolate = x => { return ''; };}
const log = console.log;// In order to support multiline cloudwatch logs we replace \n with \r.// see https://github.com/denoland/deno-lambda/issues/40// we also prefix log events with DENO_PREFIX
let requestId;const logger = (level) => { return (...args) => { const prefix = interpolate({ requestId, level }) const text = Deno[Deno.internal].inspectArgs(args); log((prefix + text).replace(/\n/g, '\r')); }}console.log = logger('INFO');console.debug = logger('DEBUG');console.info = logger('INFO');console.warn = logger('WARN');console.error = logger('ERROR');
while (true) { const next = await fetch(INVOCATION + 'next'); const headers = next.headers; requestId = headers.get('lambda-runtime-aws-request-id'); Deno.env.set('_X_AMZN_TRACE_ID', headers.get('lambda-runtime-trace-id') || ''); const context = { functionName: '$AWS_LAMBDA_FUNCTION_NAME', functionVersion: '$AWS_LAMBDA_FUNCTION_VERSION', invokedFunctionArn: headers.get('lambda-runtime-invoked-function-arn'), memoryLimitInMB: '$AWS_LAMBDA_FUNCTION_MEMORY_SIZE', awsRequestId: requestId, logGroupName: '$AWS_LAMBDA_LOG_GROUP_NAME', logStreamName: '$AWS_LAMBDA_LOG_STREAM_NAME', identity: maybeJson(headers, 'lambda-runtime-cognito-identity'), clientContext: maybeJson(headers, 'lambda-runtime-client-context'), getRemainingTimeInMillis: function() { return Number(headers.get('lambda-runtime-deadline-ms')) - Date.now(); }, // NOTE: we add these for type compatibility with Definitely Typed. callbackWaitsForEmptyEventLoop: undefined, done: undefined, fail: undefined, succeed: undefined } let res; try { const event = await next.json(); const body = await handle(event, context); res = await fetch(INVOCATION + requestId + '/response', { method: 'POST', body: JSON.stringify(body) }); } catch(e) { console.error(e); // If it's an Error we can pull these out cleanly... // BUT it's javascript so it could be anything! // If there's a better way, very happy to take suggestions. let name, message; try { name = e.name || 'Error' } catch (_) { name = 'Error' } try { message = e.message || e } catch (_) { message = e } if (typeof(name) !== 'string') { name = JSON.stringify(name) } if (typeof(message) !== 'string') { const s = JSON.stringify(message) message = s === undefined ? '' + message : s } res = await fetch(INVOCATION + requestId + '/error', { method: 'POST', body: JSON.stringify({ errorMessage: message, errorType: name }) }); } await res.blob();}" > /tmp/runtime.js
# We copy DENO_DIR into a writable directory (/tmp/deno_dir)# and expand DENO_DIR/LAMBDA_TASK_ROOT to match the new location ($LAMBDA_TASK_ROOT).
mkdir -p /tmp/deno_dir/gen/file$LAMBDA_TASK_ROOT# We cp first from the deno-lambda-layer.cp -R /opt/.deno_dir/gen/. /tmp/deno_dir/gen &> /dev/null \ && cp -R /opt/.deno_dir/deps/. /tmp/deno_dir/deps &> /dev/null \ || true# Then we overwrite with from the DENO_DIR in the function code.cp -R $LAMBDA_TASK_ROOT/$DENO_DIR/gen/. /tmp/deno_dir/gen &> /dev/null \ && cp -R $LAMBDA_TASK_ROOT/$DENO_DIR/deps/. /tmp/deno_dir/deps &> /dev/null \ && cp -R $LAMBDA_TASK_ROOT/$DENO_DIR/gen/. /tmp/deno_dir/gen &> /dev/null \ && cp -R $LAMBDA_TASK_ROOT/$DENO_DIR/LAMBDA_TASK_ROOT/. /tmp/deno_dir/gen/file$LAMBDA_TASK_ROOT &> /dev/null \ || expr match "$HANDLER_FILE" ".*bundle.js" &> /dev/null \ || echo "warn: unable to import '$DENO_DIR/' as DENO_DIR"# Note: We skip printing this warning if the filename endswith bundle.js.
# FIXME remove DENO_FLAGS=DENO_FLAGS environment variable setting (used in testing only).DENO_DIR=/tmp/deno_dir DENO_PREFIX=$DENO_PREFIX DENO_FLAGS=$DENO_FLAGS NO_COLOR=true DENO_NO_UPDATE_CHECK=true deno run $DENO_FLAGS /tmp/runtime.js \ || investigate;# For debugging purposes of the bootstrap script itself it's useful to change the extension to /tmp/runtime.ts# and run this code instead to get more verbose and type errors during testing.# Note: on production you will see the verbose error message anyway in cloudwatch logs.# DENO_DIR=/tmp/deno_dir DENO_FLAGS=$DENO_FLAGS NO_COLOR=true DENO_NO_UPDATE_CHECK=true deno run $DENO_FLAGS /tmp/runtime.ts &> /tmp/fail.out \# || error "error: $(cat /tmp/fail.out)"