Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Implementation for adding custom container support #1611

Closed
wants to merge 12 commits into from
Closed
2,618 changes: 2,603 additions & 15 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
]
},
"dependencies": {
"node-fetch": "*",
"@aws-sdk/client-lambda": "^3.509.0",
"@hapi/boom": "^10.0.1",
"@hapi/h2o2": "^10.0.4",
Expand Down
69 changes: 63 additions & 6 deletions src/ServerlessOffline.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { execa } from 'execa'
import process, { exit } from "node:process"
import { log, setLogUtils } from "./utils/log.js"

import {
commandOptions,
CUSTOM_OPTION,
Expand Down Expand Up @@ -64,6 +66,12 @@ export default class ServerlessOffline {
async start() {
this.#mergeOptions()

const images = this.#getImages()

if (images.length > 0) {
await this.#createImages(images)
}

const {
albEvents,
httpEvents,
Expand Down Expand Up @@ -170,9 +178,28 @@ export default class ServerlessOffline {
log.info(`Got ${command} signal. Offline Halting...`)
}

async #createImages(images) {
for (const image of images) {
log.info(`Building ${image.name} docker image...`)

/* eslint-disable no-await-in-loop */
await execa('docker', [
'build',
image.path,
'-t',
image.name,
'--platform',
image.platform,
])
/* eslint-enable no-await-in-loop */
}
}

async #createLambda(lambdas, skipStart) {
const { default: Lambda } = await import("./lambda/index.js")

// Implement docker handling here
//
this.#lambda = new Lambda(this.#serverless, this.#options)

this.#lambda.create(lambdas)
Expand Down Expand Up @@ -288,6 +315,27 @@ export default class ServerlessOffline {
log.debug("options:", this.#options)
}

#getImages() {
const { service } = this.#serverless
const { ecr } = service.provider
const images = []

if (ecr && ecr.images) {
const imageNames = Object.keys(ecr.images)

imageNames.forEach((name) => {
const imageDefinition = ecr.images[name]

images.push({
name: `serverless-${service.service}-${name}`,
...imageDefinition,
})
})
}

return images
}

#getEvents() {
const { service } = this.#serverless

Expand All @@ -303,10 +351,14 @@ export default class ServerlessOffline {
functionKeys.forEach((functionKey) => {
const functionDefinition = service.getFunction(functionKey)

lambdas.push({
functionDefinition,
functionKey,
})
functionDefinition.image = functionDefinition.image
? {
command: functionDefinition.image.command,
name: `serverless-${service.service}-${functionDefinition.image.name}`, // TODO: Handle flat string ref
}
: null

lambdas.push({ functionDefinition, functionKey })

const events = service.getAllEventsInFunction(functionKey) ?? []

Expand All @@ -321,21 +373,26 @@ export default class ServerlessOffline {
})
}

if (http && functionDefinition.handler) {
if (http && (functionDefinition.handler || functionDefinition.image)) {
const httpEvent = {
functionKey,
handler: functionDefinition.handler,
http,
image: functionDefinition.image,
}

httpEvents.push(httpEvent)
}

if (httpApi && functionDefinition.handler) {
if (
httpApi &&
(functionDefinition.handler || functionDefinition.image)
) {
const httpApiEvent = {
functionKey,
handler: functionDefinition.handler,
http: httpApi,
image: functionDefinition.image,
}

// Ensure definitions for 'httpApi' events are objects so that they can be marked
Expand Down
6 changes: 4 additions & 2 deletions src/events/http/Http.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ export default class Http {
}

create(events) {
events.forEach(({ functionKey, handler, http }) => {
this.#createEvent(functionKey, http, handler)
events.forEach(({ functionKey, handler, image, http }) => {
// TODO: Find a cleaner way of handling this
//
this.#createEvent(functionKey, http, image?.name || image || handler)
})

this.#httpServer.writeRoutesTerminal()
Expand Down
11 changes: 10 additions & 1 deletion src/lambda/LambdaFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export default class LambdaFunction {

#functionName = null

#image = null

#handler = null

#handlerRunner = null
Expand Down Expand Up @@ -76,12 +78,18 @@ export default class LambdaFunction {
// TODO FIXME look into better way to work with serverless-webpack
const servicepath = resolve(servicePath, options.location ?? "")

const { handler, name, package: functionPackage = {} } = functionDefinition
const {
image,
handler,
name,
package: functionPackage = {},
} = functionDefinition

// this._executionTimeout = null
this.#functionKey = functionKey
this.#functionName = name
this.#handler = handler
this.#image = image // TODO: Handle configs with a standard string reference

this.#memorySize =
functionDefinition.memorySize ??
Expand Down Expand Up @@ -151,6 +159,7 @@ export default class LambdaFunction {
? resolve(servicepath, functionPackage.artifact)
: undefined,
handler,
image,
layers: functionDefinition.layers || [],
provider,
runtime: this.#runtime,
Expand Down
18 changes: 16 additions & 2 deletions src/lambda/handler-runner/HandlerRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,23 @@ export default class HandlerRunner {

async #loadRunner() {
const { useDocker, useInProcess } = this.#options
const { handler, runtime } = this.#funOptions
const { image, runtime } = this.#funOptions

log.debug(`Loading handler... (${handler})`)
log.debug(`Loading image... (${image?.name})`) // TODO: Handle image string reference

if (image) {
const dockerOptions = {
host: this.#options.dockerHost,
hostServicePath: this.#options.dockerHostServicePath,
layersDir: this.#options.layersDir,
network: this.#options.dockerNetwork,
readOnly: this.#options.dockerReadOnly,
}

const { default: CustomRunner } = await import('./custom-runner/index.js')

return new CustomRunner(this.#funOptions, this.#env, dockerOptions)
}

if (useDocker) {
if (unsupportedDockerRuntimes.has(runtime)) {
Expand Down
Loading
Loading