Skip to content

Instantly share code, notes, and snippets.

@begoon
Last active July 4, 2024 16:08
Show Gist options
  • Save begoon/993e29f5cf9a384b9e0e96e70a71b491 to your computer and use it in GitHub Desktop.
Save begoon/993e29f5cf9a384b9e0e96e70a71b491 to your computer and use it in GitHub Desktop.
AWS lambda custom runtime for Deno or Bun
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"]
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"]
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";
}
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