deno.land / x / lambda@1.42.4 / bootstrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/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)"
lambda

Version Info

Tagged at
2 weeks ago