Last active
February 24, 2025 03:51
-
-
Save begoon/993e29f5cf9a384b9e0e96e70a71b491 to your computer and use it in GitHub Desktop.
AWS lambda custom runtime for Deno or Bun
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM oven/bun:latest as bun | |
FROM public.ecr.aws/lambda/provided:al2 | |
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun | |
# The AWS lambda filesystem is read-only, and only /tmp is writable. | |
# We need to instruct bun to use /tmp for its cache. | |
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=/tmp | |
ENV TMPDIR=/tmp | |
COPY lambda.ts /var/task/ | |
ENTRYPOINT [ "/usr/local/bin/bun" ] | |
CMD [ "/var/task/lambda.ts"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM denoland/deno as deno | |
FROM public.ecr.aws/lambda/provided:al2 | |
COPY --from=deno /usr/bin/deno /usr/bin/deno | |
# We need to set the DENO_DIR to /tmp because the AWS lambda filesystem | |
# is read-only except for /tmp. Deno may need to write to its cache. | |
ENV DENO_DIR=/tmp | |
COPY lambda.ts /var/task/ | |
ENTRYPOINT [ "/usr/bin/deno" ] | |
CMD [ "run", "-A", "--no-lock", "/var/task/lambda.ts"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import process from "node:process"; | |
const env = process.env; | |
const AWS_LAMBDA_RUNTIME_API = env.AWS_LAMBDA_RUNTIME_API || "?"; | |
console.log("AWS_LAMBDA_RUNTIME_API", AWS_LAMBDA_RUNTIME_API); | |
const API = `http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation`; | |
while (true) { | |
const event = await fetch(API + "/next"); | |
const REQUEST_ID = event.headers.get("Lambda-Runtime-Aws-Request-Id"); | |
console.log("REQUEST_ID", REQUEST_ID); | |
const response = await handler(await event.json()); | |
await fetch(API + `/${REQUEST_ID}/response`, { | |
method: "POST", | |
body: JSON.stringify(response), | |
}); | |
} | |
// This is a simplified version of the AWS Lambda runtime API. | |
// The full specification can be found at: | |
// https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html | |
type APIGatewayProxyEvent = { | |
queryStringParameters?: Record<string, string>; | |
requestContext: { http: { method: string; path: string } }; | |
body?: string; | |
}; | |
async function handler(event: APIGatewayProxyEvent) { | |
const { method, path } = event.requestContext.http; | |
const echo = { | |
method, | |
path, | |
status: "200", | |
queryStringParameters: {}, | |
runtime: runtime(), | |
env: { | |
...env, | |
AWS_SESSION_TOKEN: "REDACTED", | |
AWS_SECRET_ACCESS_KEY: "REDACTED", | |
}, | |
format: "", | |
body: "", | |
}; | |
if (event.queryStringParameters) { | |
echo.queryStringParameters = event.queryStringParameters; | |
echo.status = event.queryStringParameters.status || "200"; | |
} | |
if (event.body) { | |
try { | |
echo.body = JSON.parse(event.body); | |
echo.format = "json"; | |
} catch { | |
echo.body = event.body; | |
echo.format = "text"; | |
} | |
} | |
return { | |
statusCode: echo.status, | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify(echo), | |
}; | |
} | |
function runtime() { | |
return typeof Deno !== "undefined" | |
? "deno " + Deno.version.deno | |
: // @ts-ignore deno-ts(2867) | |
typeof Bun !== "undefined" | |
? // @ts-ignore deno-ts(2867) | |
"bun " + Bun.version | |
: "maybe node"; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
include .env | |
export | |
FUNCTION_NAME=lambda-ts-container | |
REPO = $(AWS_ACCOUNT).dkr.ecr.$(AWS_REGION).amazonaws.com | |
RUNTIME?=deno | |
all: | |
create-repo: | |
aws ecr create-repository \ | |
--profile $(AWS_PROFILE) \ | |
--repository-name $(FUNCTION_NAME) | |
create-repo-retention-policy: | |
aws ecr put-lifecycle-policy \ | |
--profile $(AWS_PROFILE) \ | |
--repository-name $(FUNCTION_NAME) \ | |
--lifecycle-policy-text file://ecr-retention-policy.json | |
build-tag-push: build tag-push | |
ecr-login: | |
aws ecr get-login-password --region $(AWS_REGION) --profile $(AWS_PROFILE) \ | |
| docker login --username AWS --password-stdin $(REPO) | |
build: | |
docker build -t $(FUNCTION_NAME) --platform linux/amd64 \ | |
-f Dockerfile-$(RUNTIME) . | |
tag-push: | |
docker tag $(FUNCTION_NAME):latest $(REPO)/$(FUNCTION_NAME):latest | |
docker push $(REPO)/$(FUNCTION_NAME):latest | |
last-tag: | |
@docker inspect $(FUNCTION_NAME):latest \ | |
| jq -r '.[0].RepoDigests[0] \ | |
| split("@")[1]' | |
# lambda | |
create-lambda-role: | |
aws iam create-role \ | |
--profile $(AWS_PROFILE) \ | |
--role-name $(FUNCTION_NAME)-role \ | |
--assume-role-policy-document \ | |
'{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' | |
aws iam attach-role-policy \ | |
--profile $(AWS_PROFILE) | |
--role-name $(FUNCTION_NAME)-role \ | |
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \ | |
create-lambda: | |
aws lambda create-function \ | |
--function-name $(FUNCTION_NAME) \ | |
--role arn:aws:iam::$(AWS_ACCOUNT):role/$(FUNCTION_NAME)-role \ | |
--package-type Image \ | |
--code ImageUri=$(REPO)/$(FUNCTION_NAME):latest \ | |
--architectures x86_64 \ | |
--profile $(AWS_PROFILE) | cat | |
create-lambda-url: | |
aws lambda create-function-url-config \ | |
--profile $(AWS_PROFILE) \ | |
--function-name $(FUNCTION_NAME) \ | |
--auth-type NONE | |
create-lambda-invoke-permission: | |
aws lambda add-permission \ | |
--profile $(AWS_PROFILE) \ | |
--function-name $(FUNCTION_NAME) \ | |
--action lambda:InvokeFunctionUrl \ | |
--statement-id FunctionURLAllowPublicAccess \ | |
--principal "*" \ | |
--function-url-auth-type NONE | |
# lambda development cycle | |
deploy: build-tag-push update-image wait | |
update-image: | |
SHA=$(shell make last-tag) && \ | |
echo "SHA=$(WHITE)$$SHA$(NC)" && \ | |
aws lambda update-function-code \ | |
--profile $(AWS_PROFILE) \ | |
--function-name $(FUNCTION_NAME) \ | |
--image $(REPO)/$(FUNCTION_NAME)@$$SHA \ | |
| jq -r '.CodeSha256' | |
status: | |
@aws lambda get-function --function-name $(FUNCTION_NAME) \ | |
--profile $(AWS_PROFILE) \ | |
| jq -r .Configuration.LastUpdateStatus | |
wait: | |
@while [ "$$(make status)" != "Successful" ]; do \ | |
echo "wait a moment for AWS to update the function..."; \ | |
sleep 10; \ | |
done | |
@echo "lambda function update complete" | |
lambda-url: | |
@aws lambda get-function-url-config --function-name $(FUNCTION_NAME) \ | |
| jq -r '.FunctionUrl | rtrimstr("/")' | |
# invoke | |
invoke: get put-json put-text | |
put-json: | |
@HOST=$(shell make lambda-url) && \ | |
http -b PUT "$$HOST/call?q=1" a=1 b="message" | |
put-text: | |
@HOST=$(shell make lambda-url) && \ | |
http -b PUT "$$HOST/call?q=1" --raw='plain data' | |
get: | |
@HOST=$(shell make lambda-url) && \ | |
http GET "$$HOST/call?a=1" | |
get-418: | |
@HOST=$(shell make lambda-url) && \ | |
http GET "$$HOST/call?a=1&status=418" | |
# test | |
test: deploy get get-418 put-json put-text |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment