serverless-offline 8.8.1 → 9.1.1

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