serverless-offline 8.8.0 → 9.1.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 (233) hide show
  1. package/README.md +12 -11
  2. package/package.json +33 -58
  3. package/src/ServerlessOffline.js +409 -0
  4. package/src/config/commandOptions.js +159 -0
  5. package/src/config/constants.js +22 -0
  6. package/{dist → src}/config/defaultOptions.js +9 -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 +1242 -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 +309 -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 +167 -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/splitHandlerPathAndName.js +31 -0
  128. package/src/utils/unflatten.js +11 -0
  129. package/dist/ServerlessOffline.js +0 -514
  130. package/dist/config/commandOptions.js +0 -149
  131. package/dist/config/constants.js +0 -30
  132. package/dist/config/index.js +0 -55
  133. package/dist/config/supportedRuntimes.js +0 -40
  134. package/dist/debugLog.js +0 -12
  135. package/dist/events/authCanExecuteResource.js +0 -35
  136. package/dist/events/authFunctionNameExtractor.js +0 -87
  137. package/dist/events/authMatchPolicyResource.js +0 -62
  138. package/dist/events/authValidateContext.js +0 -53
  139. package/dist/events/http/Endpoint.js +0 -173
  140. package/dist/events/http/Http.js +0 -77
  141. package/dist/events/http/HttpEventDefinition.js +0 -36
  142. package/dist/events/http/HttpServer.js +0 -1370
  143. package/dist/events/http/OfflineEndpoint.js +0 -38
  144. package/dist/events/http/authJWTSettingsExtractor.js +0 -76
  145. package/dist/events/http/createAuthScheme.js +0 -184
  146. package/dist/events/http/createJWTAuthScheme.js +0 -159
  147. package/dist/events/http/index.js +0 -15
  148. package/dist/events/http/javaHelpers.js +0 -99
  149. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +0 -87
  150. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +0 -246
  151. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +0 -225
  152. package/dist/events/http/lambda-events/VelocityContext.js +0 -170
  153. package/dist/events/http/lambda-events/index.js +0 -39
  154. package/dist/events/http/lambda-events/renderVelocityTemplateObject.js +0 -111
  155. package/dist/events/http/payloadSchemaValidator.js +0 -13
  156. package/dist/events/schedule/Schedule.js +0 -183
  157. package/dist/events/schedule/ScheduleEvent.js +0 -27
  158. package/dist/events/schedule/ScheduleEventDefinition.js +0 -36
  159. package/dist/events/schedule/index.js +0 -15
  160. package/dist/events/websocket/HttpServer.js +0 -114
  161. package/dist/events/websocket/WebSocket.js +0 -78
  162. package/dist/events/websocket/WebSocketClients.js +0 -577
  163. package/dist/events/websocket/WebSocketEventDefinition.js +0 -32
  164. package/dist/events/websocket/WebSocketServer.js +0 -139
  165. package/dist/events/websocket/http-routes/_catchAll/catchAllRoute.js +0 -33
  166. package/dist/events/websocket/http-routes/_catchAll/index.js +0 -15
  167. package/dist/events/websocket/http-routes/connections/ConnectionsController.js +0 -45
  168. package/dist/events/websocket/http-routes/connections/connectionsRoutes.js +0 -95
  169. package/dist/events/websocket/http-routes/connections/index.js +0 -15
  170. package/dist/events/websocket/http-routes/index.js +0 -23
  171. package/dist/events/websocket/index.js +0 -15
  172. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +0 -99
  173. package/dist/events/websocket/lambda-events/WebSocketConnectEvent.js +0 -101
  174. package/dist/events/websocket/lambda-events/WebSocketDisconnectEvent.js +0 -47
  175. package/dist/events/websocket/lambda-events/WebSocketEvent.js +0 -54
  176. package/dist/events/websocket/lambda-events/WebSocketRequestContext.js +0 -98
  177. package/dist/events/websocket/lambda-events/index.js +0 -39
  178. package/dist/index.js +0 -15
  179. package/dist/lambda/HttpServer.js +0 -124
  180. package/dist/lambda/Lambda.js +0 -117
  181. package/dist/lambda/LambdaContext.js +0 -53
  182. package/dist/lambda/LambdaFunction.js +0 -390
  183. package/dist/lambda/LambdaFunctionPool.js +0 -127
  184. package/dist/lambda/handler-runner/HandlerRunner.js +0 -195
  185. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -124
  186. package/dist/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -49
  187. package/dist/lambda/handler-runner/child-process-runner/index.js +0 -15
  188. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +0 -515
  189. package/dist/lambda/handler-runner/docker-runner/DockerImage.js +0 -67
  190. package/dist/lambda/handler-runner/docker-runner/DockerRunner.js +0 -74
  191. package/dist/lambda/handler-runner/docker-runner/index.js +0 -15
  192. package/dist/lambda/handler-runner/go-runner/GoRunner.js +0 -230
  193. package/dist/lambda/handler-runner/go-runner/index.js +0 -15
  194. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -228
  195. package/dist/lambda/handler-runner/in-process-runner/index.js +0 -15
  196. package/dist/lambda/handler-runner/index.js +0 -15
  197. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +0 -153
  198. package/dist/lambda/handler-runner/java-runner/index.js +0 -15
  199. package/dist/lambda/handler-runner/python-runner/PythonRunner.js +0 -185
  200. package/dist/lambda/handler-runner/python-runner/index.js +0 -15
  201. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -147
  202. package/dist/lambda/handler-runner/ruby-runner/index.js +0 -15
  203. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +0 -92
  204. package/dist/lambda/handler-runner/worker-thread-runner/index.js +0 -15
  205. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +0 -31
  206. package/dist/lambda/index.js +0 -15
  207. package/dist/lambda/routes/index.js +0 -23
  208. package/dist/lambda/routes/invocations/InvocationsController.js +0 -142
  209. package/dist/lambda/routes/invocations/index.js +0 -15
  210. package/dist/lambda/routes/invocations/invocationsRoute.js +0 -90
  211. package/dist/lambda/routes/invoke-async/InvokeAsyncController.js +0 -38
  212. package/dist/lambda/routes/invoke-async/index.js +0 -15
  213. package/dist/lambda/routes/invoke-async/invokeAsyncRoute.js +0 -43
  214. package/dist/main.js +0 -11
  215. package/dist/serverlessLog.js +0 -91
  216. package/dist/utils/checkDockerDaemon.js +0 -27
  217. package/dist/utils/checkGoVersion.js +0 -27
  218. package/dist/utils/createApiKey.js +0 -12
  219. package/dist/utils/createUniqueId.js +0 -14
  220. package/dist/utils/detectExecutable.js +0 -21
  221. package/dist/utils/generateHapiPath.js +0 -28
  222. package/dist/utils/getHttpApiCorsConfig.js +0 -40
  223. package/dist/utils/index.js +0 -165
  224. package/dist/utils/jsonPath.js +0 -21
  225. package/dist/utils/lowerCaseKeys.js +0 -14
  226. package/dist/utils/parseHeaders.js +0 -23
  227. package/dist/utils/parseMultiValueHeaders.js +0 -36
  228. package/dist/utils/parseMultiValueQueryStringParameters.js +0 -40
  229. package/dist/utils/parseQueryStringParameters.js +0 -26
  230. package/dist/utils/resolveJoins.js +0 -36
  231. package/dist/utils/satisfiesVersionRange.js +0 -20
  232. package/dist/utils/splitHandlerPathAndName.js +0 -37
  233. 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,167 @@
1
+ import { mkdir, readFile, rm, 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
+ // refresh go.mod
36
+ await rm(this.#tmpFile)
37
+ execaSync('go', ['mod', 'tidy'])
38
+ await rmdir(this.#tmpPath, { recursive: true })
39
+ } catch {
40
+ // @ignore
41
+ }
42
+
43
+ this.#tmpFile = null
44
+ this.#tmpPath = null
45
+ }
46
+
47
+ #parsePayload(value) {
48
+ const logs = []
49
+ let payload
50
+
51
+ for (const item of value.split(EOL)) {
52
+ if (item.indexOf(PAYLOAD_IDENTIFIER) === -1) {
53
+ logs.push(item)
54
+ } else if (item.indexOf(PAYLOAD_IDENTIFIER) !== -1) {
55
+ try {
56
+ const {
57
+ offline_payload: { success, error },
58
+ } = parse(item)
59
+
60
+ if (success) {
61
+ payload = success
62
+ } else if (error) {
63
+ payload = error
64
+ }
65
+ } catch {
66
+ // @ignore
67
+ }
68
+ }
69
+ }
70
+
71
+ // Log to console in case engineers want to see the rest of the info
72
+ log(logs.join(EOL))
73
+
74
+ return payload
75
+ }
76
+
77
+ async run(event, context) {
78
+ const { dir } = pathParse(this.#handlerPath)
79
+ const handlerCodeRoot = dir.split(sep).slice(0, -1).join(sep)
80
+ const handlerCode = await readFile(`${this.#handlerPath}.go`, 'utf8')
81
+
82
+ this.#tmpPath = resolve(handlerCodeRoot, 'tmp')
83
+ this.#tmpFile = resolve(this.#tmpPath, 'main.go')
84
+
85
+ const out = handlerCode.replace(
86
+ '"github.com/aws/aws-lambda-go/lambda"',
87
+ 'lambda "github.com/icarus-sullivan/mock-lambda"',
88
+ )
89
+
90
+ try {
91
+ await mkdir(this.#tmpPath, { recursive: true })
92
+ } catch {
93
+ // @ignore
94
+ }
95
+
96
+ try {
97
+ await writeFile(this.#tmpFile, out, 'utf8')
98
+ } catch {
99
+ // @ignore
100
+ }
101
+
102
+ // Get go env to run this locally
103
+ if (!this.#goEnv) {
104
+ const goEnvResponse = await execa('go', ['env'], {
105
+ encoding: 'utf-8',
106
+ stdio: 'pipe',
107
+ })
108
+
109
+ const goEnvString = goEnvResponse.stdout || goEnvResponse.stderr
110
+ this.#goEnv = goEnvString.split(EOL).reduce((a, b) => {
111
+ const [k, v] = b.split('="')
112
+ // eslint-disable-next-line no-param-reassign
113
+ a[k] = v ? v.slice(0, -1) : ''
114
+ return a
115
+ }, {})
116
+ }
117
+
118
+ // Remove our root, since we want to invoke go relatively
119
+ const cwdPath = `${this.#tmpFile}`.replace(`${cwd()}${sep}`, '')
120
+
121
+ try {
122
+ chdir(cwdPath.substring(0, cwdPath.indexOf('main.go')))
123
+
124
+ // Make sure we have the mock-lambda runner
125
+ execaSync('go', ['get', 'github.com/icarus-sullivan/mock-lambda@e065469'])
126
+ execaSync('go', ['build'])
127
+ } catch {
128
+ // @ignore
129
+ }
130
+
131
+ const { stdout, stderr } = await execa(`./tmp`, {
132
+ encoding: 'utf-8',
133
+ env: {
134
+ ...this.#env,
135
+ ...this.#goEnv,
136
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: context.memoryLimitInMB,
137
+ AWS_LAMBDA_FUNCTION_NAME: context.functionName,
138
+ AWS_LAMBDA_FUNCTION_VERSION: context.functionVersion,
139
+ AWS_LAMBDA_LOG_GROUP_NAME: context.logGroupName,
140
+ AWS_LAMBDA_LOG_STREAM_NAME: context.logStreamName,
141
+ IS_LAMBDA_AUTHORIZER:
142
+ event.type === 'REQUEST' || event.type === 'TOKEN',
143
+ IS_LAMBDA_REQUEST_AUTHORIZER: event.type === 'REQUEST',
144
+ IS_LAMBDA_TOKEN_AUTHORIZER: event.type === 'TOKEN',
145
+ LAMBDA_CONTEXT: stringify(context),
146
+ LAMBDA_EVENT: stringify(event),
147
+ LAMBDA_TEST_EVENT: `${event}`,
148
+ PATH: process.env.PATH,
149
+ },
150
+ stdio: 'pipe',
151
+ })
152
+
153
+ await this.cleanup()
154
+
155
+ if (stderr) {
156
+ return stderr
157
+ }
158
+
159
+ try {
160
+ chdir(this.#codeDir)
161
+ } catch {
162
+ // @ignore
163
+ }
164
+
165
+ return this.#parsePayload(stdout)
166
+ }
167
+ }
@@ -0,0 +1 @@
1
+ export { default } from './GoRunner.js'