serverless-offline 8.8.1 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +3 -3
  2. package/package.json +33 -57
  3. package/src/ServerlessOffline.js +412 -0
  4. package/src/config/commandOptions.js +155 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +8 -17
  7. package/src/config/index.js +4 -0
  8. package/src/config/supportedRuntimes.js +47 -0
  9. package/src/events/authCanExecuteResource.js +35 -0
  10. package/src/events/authFunctionNameExtractor.js +75 -0
  11. package/src/events/authMatchPolicyResource.js +71 -0
  12. package/src/events/authValidateContext.js +51 -0
  13. package/src/events/http/Endpoint.js +135 -0
  14. package/src/events/http/Http.js +50 -0
  15. package/src/events/http/HttpEventDefinition.js +20 -0
  16. package/src/events/http/HttpServer.js +1277 -0
  17. package/src/events/http/OfflineEndpoint.js +33 -0
  18. package/src/events/http/authJWTSettingsExtractor.js +70 -0
  19. package/src/events/http/createAuthScheme.js +176 -0
  20. package/src/events/http/createJWTAuthScheme.js +106 -0
  21. package/src/events/http/index.js +1 -0
  22. package/src/events/http/javaHelpers.js +102 -0
  23. package/src/events/http/lambda-events/LambdaIntegrationEvent.js +57 -0
  24. package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +233 -0
  25. package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +190 -0
  26. package/src/events/http/lambda-events/VelocityContext.js +147 -0
  27. package/src/events/http/lambda-events/index.js +4 -0
  28. package/src/events/http/lambda-events/renderVelocityTemplateObject.js +93 -0
  29. package/{dist → src}/events/http/parseResources.js +73 -78
  30. package/src/events/http/payloadSchemaValidator.js +13 -0
  31. package/{dist → src}/events/http/templates/offline-default.req.vm +0 -0
  32. package/{dist → src}/events/http/templates/offline-default.res.vm +0 -0
  33. package/src/events/schedule/Schedule.js +131 -0
  34. package/src/events/schedule/ScheduleEvent.js +18 -0
  35. package/src/events/schedule/ScheduleEventDefinition.js +21 -0
  36. package/src/events/schedule/index.js +1 -0
  37. package/src/events/websocket/HttpServer.js +69 -0
  38. package/src/events/websocket/WebSocket.js +52 -0
  39. package/src/events/websocket/WebSocketClients.js +462 -0
  40. package/src/events/websocket/WebSocketEventDefinition.js +18 -0
  41. package/src/events/websocket/WebSocketServer.js +73 -0
  42. package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +16 -0
  43. package/src/events/websocket/http-routes/_catchAll/index.js +1 -0
  44. package/src/events/websocket/http-routes/connections/ConnectionsController.js +28 -0
  45. package/src/events/websocket/http-routes/connections/connectionsRoutes.js +70 -0
  46. package/src/events/websocket/http-routes/connections/index.js +1 -0
  47. package/src/events/websocket/http-routes/index.js +2 -0
  48. package/src/events/websocket/index.js +1 -0
  49. package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +65 -0
  50. package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +68 -0
  51. package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +31 -0
  52. package/src/events/websocket/lambda-events/WebSocketEvent.js +29 -0
  53. package/src/events/websocket/lambda-events/WebSocketRequestContext.js +67 -0
  54. package/src/events/websocket/lambda-events/index.js +4 -0
  55. package/src/index.js +12 -0
  56. package/src/lambda/HttpServer.js +108 -0
  57. package/src/lambda/Lambda.js +68 -0
  58. package/src/lambda/LambdaContext.js +33 -0
  59. package/src/lambda/LambdaFunction.js +308 -0
  60. package/src/lambda/LambdaFunctionPool.js +109 -0
  61. package/src/lambda/__tests__/LambdaContext.test.js +30 -0
  62. package/src/lambda/__tests__/LambdaFunction.test.js +196 -0
  63. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsJSONObject.fixture.js +47 -0
  64. package/src/lambda/__tests__/fixtures/Lambda/LambdaFunctionThatReturnsNativeString.fixture.js +46 -0
  65. package/src/lambda/__tests__/fixtures/Lambda/package.json +3 -0
  66. package/src/lambda/__tests__/fixtures/lambdaFunction.fixture.js +145 -0
  67. package/src/lambda/__tests__/fixtures/package.json +3 -0
  68. package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +42 -0
  69. package/src/lambda/handler-runner/HandlerRunner.js +136 -0
  70. package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +72 -0
  71. package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +42 -0
  72. package/src/lambda/handler-runner/child-process-runner/index.js +1 -0
  73. package/src/lambda/handler-runner/docker-runner/DockerContainer.js +417 -0
  74. package/src/lambda/handler-runner/docker-runner/DockerImage.js +35 -0
  75. package/src/lambda/handler-runner/docker-runner/DockerRunner.js +63 -0
  76. package/src/lambda/handler-runner/docker-runner/index.js +1 -0
  77. package/src/lambda/handler-runner/go-runner/GoRunner.js +166 -0
  78. package/src/lambda/handler-runner/go-runner/index.js +1 -0
  79. package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +125 -0
  80. package/src/lambda/handler-runner/in-process-runner/index.js +1 -0
  81. package/src/lambda/handler-runner/index.js +1 -0
  82. package/src/lambda/handler-runner/java-runner/JavaRunner.js +114 -0
  83. package/src/lambda/handler-runner/java-runner/index.js +1 -0
  84. package/src/lambda/handler-runner/python-runner/PythonRunner.js +138 -0
  85. package/src/lambda/handler-runner/python-runner/index.js +1 -0
  86. package/{dist → src}/lambda/handler-runner/python-runner/invoke.py +0 -0
  87. package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +107 -0
  88. package/src/lambda/handler-runner/ruby-runner/index.js +1 -0
  89. package/{dist → src}/lambda/handler-runner/ruby-runner/invoke.rb +0 -0
  90. package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +70 -0
  91. package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -0
  92. package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +29 -0
  93. package/src/lambda/index.js +1 -0
  94. package/src/lambda/routes/index.js +2 -0
  95. package/src/lambda/routes/invocations/InvocationsController.js +102 -0
  96. package/src/lambda/routes/invocations/index.js +1 -0
  97. package/src/lambda/routes/invocations/invocationsRoute.js +77 -0
  98. package/src/lambda/routes/invoke-async/InvokeAsyncController.js +20 -0
  99. package/src/lambda/routes/invoke-async/index.js +1 -0
  100. package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +33 -0
  101. package/src/utils/__tests__/createUniqueId.test.js +18 -0
  102. package/src/utils/__tests__/formatToClfTime.test.js +14 -0
  103. package/src/utils/__tests__/generateHapiPath.test.js +46 -0
  104. package/src/utils/__tests__/lowerCaseKeys.test.js +30 -0
  105. package/src/utils/__tests__/parseHeaders.test.js +13 -0
  106. package/src/utils/__tests__/parseMultiValueHeaders.test.js +24 -0
  107. package/src/utils/__tests__/parseMultiValueQueryStringParameters.test.js +159 -0
  108. package/src/utils/__tests__/parseQueryStringParameters.test.js +15 -0
  109. package/src/utils/__tests__/splitHandlerPathAndName.test.js +54 -0
  110. package/src/utils/__tests__/unflatten.test.js +32 -0
  111. package/src/utils/checkDockerDaemon.js +19 -0
  112. package/src/utils/checkGoVersion.js +16 -0
  113. package/src/utils/createApiKey.js +5 -0
  114. package/src/utils/createUniqueId.js +5 -0
  115. package/src/utils/detectExecutable.js +11 -0
  116. package/{dist → src}/utils/formatToClfTime.js +6 -14
  117. package/src/utils/generateHapiPath.js +26 -0
  118. package/src/utils/getHttpApiCorsConfig.js +28 -0
  119. package/src/utils/index.js +42 -0
  120. package/src/utils/jsonPath.js +13 -0
  121. package/src/utils/logRoutes.js +64 -0
  122. package/src/utils/lowerCaseKeys.js +6 -0
  123. package/src/utils/parseHeaders.js +14 -0
  124. package/src/utils/parseMultiValueHeaders.js +27 -0
  125. package/src/utils/parseMultiValueQueryStringParameters.js +31 -0
  126. package/src/utils/parseQueryStringParameters.js +15 -0
  127. package/src/utils/resolveJoins.js +29 -0
  128. package/src/utils/splitHandlerPathAndName.js +31 -0
  129. package/src/utils/unflatten.js +11 -0
  130. package/CHANGELOG.md +0 -78
  131. package/dist/ServerlessOffline.js +0 -508
  132. package/dist/config/commandOptions.js +0 -149
  133. package/dist/config/constants.js +0 -30
  134. package/dist/config/index.js +0 -55
  135. package/dist/config/supportedRuntimes.js +0 -40
  136. package/dist/debugLog.js +0 -12
  137. package/dist/events/authCanExecuteResource.js +0 -35
  138. package/dist/events/authFunctionNameExtractor.js +0 -87
  139. package/dist/events/authMatchPolicyResource.js +0 -62
  140. package/dist/events/authValidateContext.js +0 -53
  141. package/dist/events/http/Endpoint.js +0 -173
  142. package/dist/events/http/Http.js +0 -77
  143. package/dist/events/http/HttpEventDefinition.js +0 -36
  144. package/dist/events/http/HttpServer.js +0 -1370
  145. package/dist/events/http/OfflineEndpoint.js +0 -38
  146. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  147. package/dist/events/http/createAuthScheme.js +0 -184
  148. package/dist/events/http/createJWTAuthScheme.js +0 -159
  149. package/dist/events/http/index.js +0 -15
  150. package/dist/events/http/javaHelpers.js +0 -99
  151. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  152. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  153. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  154. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  155. package/dist/events/http/lambda-events/index.js +0 -39
  156. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  157. package/dist/events/http/payloadSchemaValidator.js +0 -13
  158. package/dist/events/schedule/Schedule.js +0 -183
  159. package/dist/events/schedule/ScheduleEvent.js +0 -27
  160. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  161. package/dist/events/schedule/index.js +0 -15
  162. package/dist/events/websocket/HttpServer.js +0 -114
  163. package/dist/events/websocket/WebSocket.js +0 -78
  164. package/dist/events/websocket/WebSocketClients.js +0 -577
  165. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  166. package/dist/events/websocket/WebSocketServer.js +0 -139
  167. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  168. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  169. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  170. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  171. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  172. package/dist/events/websocket/http-routes/index.js +0 -23
  173. package/dist/events/websocket/index.js +0 -15
  174. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  175. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  176. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  177. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  178. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  179. package/dist/events/websocket/lambda-events/index.js +0 -39
  180. package/dist/index.js +0 -15
  181. package/dist/lambda/HttpServer.js +0 -124
  182. package/dist/lambda/Lambda.js +0 -117
  183. package/dist/lambda/LambdaContext.js +0 -53
  184. package/dist/lambda/LambdaFunction.js +0 -390
  185. package/dist/lambda/LambdaFunctionPool.js +0 -127
  186. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  187. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  188. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  189. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  190. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  191. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  192. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  193. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  195. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  197. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  198. package/dist/lambda/handler-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  200. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  202. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  204. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  206. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  207. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  208. package/dist/lambda/index.js +0 -15
  209. package/dist/lambda/routes/index.js +0 -23
  210. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  211. package/dist/lambda/routes/invocations/index.js +0 -15
  212. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  213. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  214. package/dist/lambda/routes/invoke-async/index.js +0 -15
  215. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  216. package/dist/main.js +0 -11
  217. package/dist/serverlessLog.js +0 -91
  218. package/dist/utils/checkDockerDaemon.js +0 -27
  219. package/dist/utils/checkGoVersion.js +0 -27
  220. package/dist/utils/createApiKey.js +0 -12
  221. package/dist/utils/createUniqueId.js +0 -14
  222. package/dist/utils/detectExecutable.js +0 -21
  223. package/dist/utils/generateHapiPath.js +0 -28
  224. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  225. package/dist/utils/index.js +0 -165
  226. package/dist/utils/jsonPath.js +0 -21
  227. package/dist/utils/lowerCaseKeys.js +0 -14
  228. package/dist/utils/parseHeaders.js +0 -23
  229. package/dist/utils/parseMultiValueHeaders.js +0 -36
  230. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  231. package/dist/utils/parseQueryStringParameters.js +0 -26
  232. package/dist/utils/resolveJoins.js +0 -36
  233. package/dist/utils/satisfiesVersionRange.js +0 -20
  234. package/dist/utils/splitHandlerPathAndName.js +0 -37
  235. package/dist/utils/unflatten.js +0 -18
@@ -0,0 +1,417 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { createWriteStream } from 'node:fs'
3
+ import { readFile, unlink, writeFile } from 'node:fs/promises'
4
+ import { platform } from 'node:os'
5
+ import { dirname, join, sep } from 'node:path'
6
+ import { log, progress } from '@serverless/utils/log.js'
7
+ import aws from 'aws-sdk'
8
+ import { execa } from 'execa'
9
+ import { ensureDir, pathExists } from 'fs-extra'
10
+ import jszip from 'jszip'
11
+ import pRetry from 'p-retry'
12
+ import DockerImage from './DockerImage.js'
13
+
14
+ const { stringify } = JSON
15
+ const { entries, hasOwn } = Object
16
+
17
+ export default class DockerContainer {
18
+ #containerId = null
19
+
20
+ #dockerOptions = null
21
+
22
+ #env = null
23
+
24
+ #functionKey = null
25
+
26
+ #handler = null
27
+
28
+ #image = null
29
+
30
+ #imageNameTag = null
31
+
32
+ #lambda = null
33
+
34
+ #layers = null
35
+
36
+ #port = null
37
+
38
+ #provider = null
39
+
40
+ #runtime = null
41
+
42
+ #servicePath = null
43
+
44
+ constructor(
45
+ env,
46
+ functionKey,
47
+ handler,
48
+ runtime,
49
+ layers,
50
+ provider,
51
+ servicePath,
52
+ dockerOptions,
53
+ ) {
54
+ this.#dockerOptions = dockerOptions
55
+ this.#env = env
56
+ this.#functionKey = functionKey
57
+ this.#handler = handler
58
+ this.#imageNameTag = this.#baseImage(runtime)
59
+ this.#image = new DockerImage(this.#imageNameTag)
60
+ this.#layers = layers
61
+ this.#provider = provider
62
+ this.#runtime = runtime
63
+ this.#servicePath = servicePath
64
+ }
65
+
66
+ #baseImage(runtime) {
67
+ return `lambci/lambda:${runtime}`
68
+ }
69
+
70
+ async start(codeDir) {
71
+ await this.#image.pull()
72
+
73
+ log.debug('Run Docker container...')
74
+
75
+ let permissions = 'ro'
76
+
77
+ if (!this.#dockerOptions.readOnly) {
78
+ permissions = 'rw'
79
+ }
80
+ // https://github.com/serverless/serverless/blob/v1.57.0/lib/plugins/aws/invokeLocal/index.js#L291-L293
81
+ const dockerArgs = [
82
+ '-v',
83
+ `${codeDir}:/var/task:${permissions},delegated`,
84
+ '-p',
85
+ 9001,
86
+ '-e',
87
+ 'DOCKER_LAMBDA_STAY_OPEN=1', // API mode
88
+ '-e',
89
+ 'DOCKER_LAMBDA_WATCH=1', // Watch mode
90
+ ]
91
+
92
+ if (this.#layers.length > 0) {
93
+ log.verbose(`Found layers, checking provider type`)
94
+
95
+ if (this.#provider.name.toLowerCase() !== 'aws') {
96
+ log.warning(
97
+ `Provider ${
98
+ this.#provider.name
99
+ } is Unsupported. Layers are only supported on aws.`,
100
+ )
101
+ } else {
102
+ let layerDir = this.#dockerOptions.layersDir
103
+
104
+ if (!layerDir) {
105
+ layerDir = join(this.#servicePath, '.serverless-offline', 'layers')
106
+ }
107
+
108
+ layerDir = join(layerDir, this.#getLayersSha256())
109
+
110
+ if (await pathExists(layerDir)) {
111
+ log.verbose(
112
+ `Layers already exist for this function. Skipping download.`,
113
+ )
114
+ } else {
115
+ const layers = []
116
+
117
+ log.verbose(`Storing layers at ${layerDir}`)
118
+
119
+ // Only initialise if we have layers, we're using AWS, and they don't already exist
120
+ this.#lambda = new aws.Lambda({
121
+ apiVersion: '2015-03-31',
122
+ region: this.#provider.region,
123
+ })
124
+
125
+ log.verbose(`Getting layers`)
126
+
127
+ for (const layerArn of this.#layers) {
128
+ layers.push(this.#downloadLayer(layerArn, layerDir))
129
+ }
130
+
131
+ await Promise.all(layers)
132
+ }
133
+
134
+ if (
135
+ this.#dockerOptions.hostServicePath &&
136
+ layerDir.startsWith(this.#servicePath)
137
+ ) {
138
+ layerDir = layerDir.replace(
139
+ this.#servicePath,
140
+ this.#dockerOptions.hostServicePath,
141
+ )
142
+ }
143
+ dockerArgs.push('-v', `${layerDir}:/opt:ro,delegated`)
144
+ }
145
+ }
146
+
147
+ entries(this.#env).forEach(([key, value]) => {
148
+ dockerArgs.push('-e', `${key}=${value}`)
149
+ })
150
+
151
+ if (platform() === 'linux') {
152
+ // Add `host.docker.internal` DNS name to access host from inside the container
153
+ // https://github.com/docker/for-linux/issues/264
154
+ const gatewayIp = await this.#getBridgeGatewayIp()
155
+ if (gatewayIp) {
156
+ dockerArgs.push('--add-host', `host.docker.internal:${gatewayIp}`)
157
+ }
158
+ }
159
+
160
+ if (this.#dockerOptions.network) {
161
+ dockerArgs.push('--network', this.#dockerOptions.network)
162
+ }
163
+
164
+ const { stdout: containerId } = await execa('docker', [
165
+ 'create',
166
+ ...dockerArgs,
167
+ this.#imageNameTag,
168
+ this.#handler,
169
+ ])
170
+
171
+ const dockerStart = execa('docker', ['start', '-a', containerId], {
172
+ all: true,
173
+ })
174
+
175
+ await new Promise((resolve, reject) => {
176
+ dockerStart.all.on('data', (data) => {
177
+ const str = String(data)
178
+ log.error(str)
179
+
180
+ if (str.includes('Lambda API listening on port')) {
181
+ resolve()
182
+ }
183
+ })
184
+
185
+ dockerStart.on('error', (err) => {
186
+ reject(err)
187
+ })
188
+ })
189
+
190
+ // parse `docker port` output and get the container port
191
+ let containerPort
192
+ const { stdout: dockerPortOutput } = await execa('docker', [
193
+ 'port',
194
+ containerId,
195
+ ])
196
+ // NOTE: `docker port` may output multiple lines.
197
+ //
198
+ // e.g.:
199
+ // 9001/tcp -> 0.0.0.0:49153
200
+ // 9001/tcp -> :::49153
201
+ //
202
+ // Parse each line until it finds the mapped port.
203
+ for (const line of dockerPortOutput.split('\n')) {
204
+ const result = line.match(/^9001\/tcp -> (.*):(\d+)$/)
205
+ if (result && result.length > 2) {
206
+ ;[, , containerPort] = result
207
+ break
208
+ }
209
+ }
210
+ if (!containerPort) {
211
+ throw new Error('Failed to get container port')
212
+ }
213
+
214
+ this.#containerId = containerId
215
+ this.#port = containerPort
216
+
217
+ await pRetry(() => this.#ping(), {
218
+ // default,
219
+ factor: 2,
220
+ // milliseconds
221
+ minTimeout: 10,
222
+ // default
223
+ retries: 10,
224
+ })
225
+ }
226
+
227
+ async #downloadLayer(layerArn, layerDir) {
228
+ const layerName = layerArn.split(':layer:')[1]
229
+ const layerZipFile = `${layerDir}/${layerName}.zip`
230
+ const layerProgress = progress.get(`layer-${layerName}`)
231
+
232
+ log.verbose(`[${layerName}] ARN: ${layerArn}`)
233
+
234
+ const params = {
235
+ Arn: layerArn,
236
+ }
237
+
238
+ log.verbose(`[${layerName}] Getting Info`)
239
+ layerProgress.notice(`Retrieving "${layerName}": Getting info`)
240
+
241
+ try {
242
+ let layer = null
243
+
244
+ try {
245
+ layer = await this.#lambda.getLayerVersionByArn(params).promise()
246
+ } catch (err) {
247
+ log.warning(`[${layerName}] ${err.code}: ${err.message}`)
248
+
249
+ return
250
+ }
251
+
252
+ if (
253
+ hasOwn(layer, 'CompatibleRuntimes') &&
254
+ !layer.CompatibleRuntimes.includes(this.#runtime)
255
+ ) {
256
+ log.warning(
257
+ `[${layerName}] Layer is not compatible with ${
258
+ this.#runtime
259
+ } runtime`,
260
+ )
261
+
262
+ return
263
+ }
264
+
265
+ const layerUrl = layer.Content.Location
266
+ // const layerSha = layer.Content.CodeSha256
267
+
268
+ const layerSize = layer.Content.CodeSize
269
+
270
+ await ensureDir(layerDir)
271
+
272
+ log.verbose(
273
+ `Retrieving "${layerName}": Downloading ${this.#formatBytes(
274
+ layerSize,
275
+ )}...`,
276
+ )
277
+ layerProgress.notice(
278
+ `Retrieving "${layerName}": Downloading ${this.#formatBytes(
279
+ layerSize,
280
+ )}`,
281
+ )
282
+
283
+ const res = await fetch(layerUrl, {
284
+ method: 'get',
285
+ })
286
+
287
+ if (!res.ok) {
288
+ log.warning(
289
+ `[${layerName}] Failed to fetch from ${layerUrl} with ${res.statusText}`,
290
+ )
291
+
292
+ return
293
+ }
294
+
295
+ const fileStream = createWriteStream(layerZipFile)
296
+
297
+ await new Promise((resolve, reject) => {
298
+ res.body.pipe(fileStream)
299
+ res.body.on('error', (err) => {
300
+ reject(err)
301
+ })
302
+ fileStream.on('finish', () => {
303
+ resolve()
304
+ })
305
+ })
306
+
307
+ log.verbose(`Retrieving "${layerName}": Unzipping to .layers directory`)
308
+ layerProgress.notice(
309
+ `Retrieving "${layerName}": Unzipping to .layers directory`,
310
+ )
311
+
312
+ const data = await readFile(layerZipFile)
313
+ const zip = await jszip.loadAsync(data)
314
+
315
+ await Promise.all(
316
+ entries(zip.files).map(async ([filename, jsZipObj]) => {
317
+ const fileData = await jsZipObj.async('nodebuffer')
318
+ if (filename.endsWith(sep)) {
319
+ return Promise.resolve()
320
+ }
321
+ await ensureDir(join(layerDir, dirname(filename)))
322
+ return writeFile(join(layerDir, filename), fileData, {
323
+ mode: zip.files[filename].unixPermissions,
324
+ })
325
+ }),
326
+ )
327
+
328
+ log.verbose(`[${layerName}] Removing zip file`)
329
+
330
+ await unlink(layerZipFile)
331
+ } finally {
332
+ layerProgress.remove()
333
+ }
334
+ }
335
+
336
+ async #getBridgeGatewayIp() {
337
+ let gateway
338
+ try {
339
+ ;({ stdout: gateway } = await execa('docker', [
340
+ 'network',
341
+ 'inspect',
342
+ 'bridge',
343
+ '--format',
344
+ '{{(index .IPAM.Config 0).Gateway}}',
345
+ ]))
346
+ } catch (err) {
347
+ log.error(err.stderr)
348
+
349
+ throw err
350
+ }
351
+ return gateway.split('/')[0]
352
+ }
353
+
354
+ async #ping() {
355
+ const url = `http://${this.#dockerOptions.host}:${
356
+ this.#port
357
+ }/2018-06-01/ping`
358
+ const res = await fetch(url)
359
+
360
+ if (!res.ok) {
361
+ throw new Error(`Failed to fetch from ${url} with ${res.statusText}`)
362
+ }
363
+
364
+ return res.text()
365
+ }
366
+
367
+ async request(event) {
368
+ const url = `http://${this.#dockerOptions.host}:${
369
+ this.#port
370
+ }/2015-03-31/functions/${this.#functionKey}/invocations`
371
+
372
+ const res = await fetch(url, {
373
+ body: stringify(event),
374
+ headers: { 'Content-Type': 'application/json' },
375
+ method: 'post',
376
+ })
377
+
378
+ if (!res.ok) {
379
+ throw new Error(`Failed to fetch from ${url} with ${res.statusText}`)
380
+ }
381
+
382
+ return res.json()
383
+ }
384
+
385
+ async stop() {
386
+ if (this.#containerId) {
387
+ try {
388
+ await execa('docker', ['stop', this.#containerId])
389
+ await execa('docker', ['rm', this.#containerId])
390
+ } catch (err) {
391
+ log.error(err.stderr)
392
+
393
+ throw err
394
+ }
395
+ }
396
+ }
397
+
398
+ #formatBytes(bytes, decimals = 2) {
399
+ if (bytes === 0) return '0 Bytes'
400
+
401
+ const k = 1024
402
+ const dm = decimals < 0 ? 0 : decimals
403
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
404
+
405
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
406
+
407
+ return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`
408
+ }
409
+
410
+ #getLayersSha256() {
411
+ return createHash('sha256').update(stringify(this.#layers)).digest('hex')
412
+ }
413
+
414
+ get isRunning() {
415
+ return this.#containerId !== null && this.#port !== null
416
+ }
417
+ }
@@ -0,0 +1,35 @@
1
+ import { log } from '@serverless/utils/log.js'
2
+ import { execa } from 'execa'
3
+ // TODO FIXME eslint plugin import bug, or not supporting package.json "expprts" field?
4
+ // eslint-disable-next-line import/no-unresolved
5
+ import promiseMemoize from 'p-memoize'
6
+
7
+ export default class DockerImage {
8
+ #imageNameTag = null
9
+
10
+ static #memoizedPull = promiseMemoize(DockerImage.#pullImage)
11
+
12
+ constructor(imageNameTag) {
13
+ this.#imageNameTag = imageNameTag
14
+ }
15
+
16
+ static async #pullImage(imageNameTag) {
17
+ log.debug(`Downloading base Docker image... (${imageNameTag})`)
18
+
19
+ try {
20
+ await execa('docker', [
21
+ 'pull',
22
+ '--disable-content-trust=false',
23
+ imageNameTag,
24
+ ])
25
+ } catch (err) {
26
+ log.error(err.stderr)
27
+
28
+ throw err
29
+ }
30
+ }
31
+
32
+ async pull() {
33
+ return DockerImage.#memoizedPull(this.#imageNameTag)
34
+ }
35
+ }
@@ -0,0 +1,63 @@
1
+ import DockerContainer from './DockerContainer.js'
2
+ import { checkDockerDaemon } from '../../../utils/index.js'
3
+
4
+ export default class DockerRunner {
5
+ #codeDir = null
6
+
7
+ #container = null
8
+
9
+ constructor(funOptions, env, dockerOptions) {
10
+ const {
11
+ codeDir,
12
+ functionKey,
13
+ handler,
14
+ runtime,
15
+ layers,
16
+ provider,
17
+ servicePath,
18
+ } = funOptions
19
+
20
+ this.#codeDir = codeDir
21
+
22
+ if (
23
+ dockerOptions.hostServicePath &&
24
+ this.#codeDir.startsWith(servicePath)
25
+ ) {
26
+ this.#codeDir = this.#codeDir.replace(
27
+ servicePath,
28
+ dockerOptions.hostServicePath,
29
+ )
30
+ }
31
+
32
+ this.#container = new DockerContainer(
33
+ env,
34
+ functionKey,
35
+ handler,
36
+ runtime,
37
+ layers,
38
+ provider,
39
+ servicePath,
40
+ dockerOptions,
41
+ )
42
+ }
43
+
44
+ cleanup() {
45
+ if (this.#container) {
46
+ return this.#container.stop()
47
+ }
48
+
49
+ return undefined
50
+ }
51
+
52
+ // context will be generated in container
53
+ async run(event) {
54
+ // FIXME TODO this should run only once -> static private
55
+ await checkDockerDaemon()
56
+
57
+ if (!this.#container.isRunning) {
58
+ await this.#container.start(this.#codeDir)
59
+ }
60
+
61
+ return this.#container.request(event)
62
+ }
63
+ }
@@ -0,0 +1 @@
1
+ export { default } from './DockerRunner.js'
@@ -0,0 +1,166 @@
1
+ import { mkdir, readFile, rmdir, writeFile } from 'node:fs/promises'
2
+ import { EOL } from 'node:os'
3
+ import { sep, resolve, parse as pathParse } from 'node:path'
4
+ import process, { chdir, cwd } from 'node:process'
5
+ import { log } from '@serverless/utils/log.js'
6
+ import { execa, execaSync } from 'execa'
7
+
8
+ const { parse, stringify } = JSON
9
+
10
+ const PAYLOAD_IDENTIFIER = 'offline_payload'
11
+
12
+ export default class GoRunner {
13
+ #codeDir = null
14
+
15
+ #env = null
16
+
17
+ #goEnv = null
18
+
19
+ #handlerPath = null
20
+
21
+ #tmpPath = null
22
+
23
+ #tmpFile = null
24
+
25
+ constructor(funOptions, env) {
26
+ const { handlerPath, codeDir } = funOptions
27
+
28
+ this.#codeDir = codeDir
29
+ this.#env = env
30
+ this.#handlerPath = handlerPath
31
+ }
32
+
33
+ async cleanup() {
34
+ try {
35
+ await rmdir(this.#tmpPath, { recursive: true })
36
+ } catch {
37
+ // @ignore
38
+ }
39
+
40
+ this.#tmpFile = null
41
+ this.#tmpPath = null
42
+ }
43
+
44
+ #parsePayload(value) {
45
+ const logs = []
46
+ let payload
47
+
48
+ for (const item of value.split(EOL)) {
49
+ if (item.indexOf(PAYLOAD_IDENTIFIER) === -1) {
50
+ logs.push(item)
51
+ } else if (item.indexOf(PAYLOAD_IDENTIFIER) !== -1) {
52
+ try {
53
+ const {
54
+ offline_payload: { success, error },
55
+ } = parse(item)
56
+
57
+ if (success) {
58
+ payload = success
59
+ } else if (error) {
60
+ payload = error
61
+ }
62
+ } catch {
63
+ // @ignore
64
+ }
65
+ }
66
+ }
67
+
68
+ // Log to console in case engineers want to see the rest of the info
69
+ log(logs.join(EOL))
70
+
71
+ return payload
72
+ }
73
+
74
+ async run(event, context) {
75
+ const { dir } = pathParse(this.#handlerPath)
76
+ const handlerCodeRoot = dir.split(sep).slice(0, -1).join(sep)
77
+ const handlerCode = await readFile(`${this.#handlerPath}.go`, 'utf8')
78
+
79
+ this.#tmpPath = resolve(handlerCodeRoot, 'tmp')
80
+ this.#tmpFile = resolve(this.#tmpPath, 'main.go')
81
+
82
+ const out = handlerCode.replace(
83
+ '"github.com/aws/aws-lambda-go/lambda"',
84
+ 'lambda "github.com/icarus-sullivan/mock-lambda"',
85
+ )
86
+
87
+ try {
88
+ await mkdir(this.#tmpPath, { recursive: true })
89
+ } catch {
90
+ // @ignore
91
+ }
92
+
93
+ try {
94
+ await writeFile(this.#tmpFile, out, 'utf8')
95
+ } catch {
96
+ // @ignore
97
+ }
98
+
99
+ // Get go env to run this locally
100
+ if (!this.#goEnv) {
101
+ const goEnvResponse = await execa('go', ['env'], {
102
+ encoding: 'utf-8',
103
+ stdio: 'pipe',
104
+ })
105
+
106
+ const goEnvString = goEnvResponse.stdout || goEnvResponse.stderr
107
+ this.#goEnv = goEnvString.split(EOL).reduce((a, b) => {
108
+ const [k, v] = b.split('="')
109
+ // eslint-disable-next-line no-param-reassign
110
+ a[k] = v ? v.slice(0, -1) : ''
111
+ return a
112
+ }, {})
113
+ }
114
+
115
+ // Remove our root, since we want to invoke go relatively
116
+ const cwdPath = `${this.#tmpFile}`.replace(`${cwd()}${sep}`, '')
117
+
118
+ try {
119
+ chdir(cwdPath.substring(0, cwdPath.indexOf('main.go')))
120
+
121
+ // Make sure we have the mock-lambda runner
122
+ execaSync('go', ['get', 'github.com/icarus-sullivan/mock-lambda@e065469'])
123
+ execaSync('go', ['build'])
124
+ } catch {
125
+ // @ignore
126
+ }
127
+
128
+ const { stdout, stderr } = await execa(`./tmp`, {
129
+ encoding: 'utf-8',
130
+ env: {
131
+ ...this.#env,
132
+ ...this.#goEnv,
133
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: context.memoryLimitInMB,
134
+ AWS_LAMBDA_FUNCTION_NAME: context.functionName,
135
+ AWS_LAMBDA_FUNCTION_VERSION: context.functionVersion,
136
+ AWS_LAMBDA_LOG_GROUP_NAME: context.logGroupName,
137
+ AWS_LAMBDA_LOG_STREAM_NAME: context.logStreamName,
138
+ IS_LAMBDA_AUTHORIZER:
139
+ event.type === 'REQUEST' || event.type === 'TOKEN',
140
+ IS_LAMBDA_REQUEST_AUTHORIZER: event.type === 'REQUEST',
141
+ IS_LAMBDA_TOKEN_AUTHORIZER: event.type === 'TOKEN',
142
+ LAMBDA_CONTEXT: stringify(context),
143
+ LAMBDA_EVENT: stringify(event),
144
+ LAMBDA_TEST_EVENT: `${event}`,
145
+ PATH: process.env.PATH,
146
+ },
147
+ stdio: 'pipe',
148
+ })
149
+
150
+ await this.cleanup()
151
+
152
+ if (stderr) {
153
+ return stderr
154
+ }
155
+
156
+ try {
157
+ // refresh go.mod
158
+ execaSync('go', ['mod', 'tidy'])
159
+ chdir(this.#codeDir)
160
+ } catch {
161
+ // @ignore
162
+ }
163
+
164
+ return this.#parsePayload(stdout)
165
+ }
166
+ }
@@ -0,0 +1 @@
1
+ export { default } from './GoRunner.js'