New boilerplate for an openapi stack running via AWS Lambda Function
This commit is contained in:
commit
a0c1e85867
251
.gitignore
vendored
Normal file
251
.gitignore
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### OSX ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Ruby plugin and RubyMine
|
||||
/.rakeTasks
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### PyCharm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule.*
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Build folder
|
||||
|
||||
*/build/*
|
||||
|
||||
# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
|
||||
.aws-sam
|
||||
*.log
|
||||
archive/
|
||||
layer/opt/deno
|
||||
.tmp_deno_dir
|
||||
.terraform
|
||||
*.tfstate*
|
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"denoland.vscode-deno"
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"editor.formatOnSave": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||
}
|
||||
}
|
127
README.md
Normal file
127
README.md
Normal file
@ -0,0 +1,127 @@
|
||||
# Hello OpenAPI
|
||||
|
||||
This repo contains boilerplate code for building AWS Lambda functions using Docker. See [notes/decisions.md](notes/decisions.md) for stack decisions.
|
||||
|
||||
Sample containers are provided:
|
||||
|
||||
* [hello-deno](containers/hello-deno/) - simplest example lambda function using Deno
|
||||
* [hello-openapi](containers/hello-openapi/) - opinionated OpenAPI stack
|
||||
|
||||
## IDE Note
|
||||
|
||||
In VSCode, import module resolution can be handled by the `denoland.vscode-deno`. They suggest keeping it disabled globally and only enabling it per workspace.
|
||||
|
||||
![enable deno extension in workspace](notes/ide-deno-extension.png)
|
||||
|
||||
## Build Docker Image
|
||||
|
||||
Build and test the docker image locally.
|
||||
|
||||
```sh
|
||||
export CONTAINER_NAME=hello-openapi
|
||||
docker build -t $CONTAINER_NAME .
|
||||
```
|
||||
|
||||
(Optional) Run the container locally.
|
||||
|
||||
```sh
|
||||
docker run -p 8000:8000 $CONTAINER_NAME:latest
|
||||
curl http://localhost:8000
|
||||
docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Overview:
|
||||
|
||||
1. Create the ECR repository.
|
||||
2. Push the container image to the ECR repository.
|
||||
3. Deploy the lambda function.
|
||||
|
||||
### Create the ECR repository
|
||||
|
||||
You'll first need to authenticate with AWS.
|
||||
|
||||
```sh
|
||||
export AWS_REGION=us-west-1
|
||||
export AWS_PROFILE=playground
|
||||
aws configure sso --profile $AWS_PROFILE
|
||||
```
|
||||
|
||||
With Terraform:
|
||||
|
||||
```sh
|
||||
cd terraform
|
||||
terraform init
|
||||
terraform apply -target=aws_ecr_repository.hello_docker
|
||||
export REPOSITORY_URI=$(terraform output -raw ecr_repository_url)
|
||||
aws ecr get-login-password --region $AWS_REGION --profile $AWS_PROFILE | docker login --username AWS --password-stdin $REPOSITORY_URI
|
||||
docker tag $CONTAINER_NAME:latest $REPOSITORY_URI:latest
|
||||
docker push $REPOSITORY_URI:latest
|
||||
```
|
||||
|
||||
With aws-sam-cli:
|
||||
|
||||
```sh
|
||||
cd sam
|
||||
sam build
|
||||
sam deploy --guided --profile playground --region us-west-1 --parameter-overrides DeployECROnly=true
|
||||
export REPOSITORY_URI=$(aws ecr describe-repositories --repository-names $CONTAINER_NAME --region $AWS_REGION --profile $AWS_PROFILE | jq -r '.repositories[0].repositoryUri')
|
||||
aws ecr get-login-password --region $AWS_REGION --profile $AWS_PROFILE | docker login --username AWS --password-stdin $REPOSITORY_URI
|
||||
docker tag $CONTAINER_NAME:latest $REPOSITORY_URI:latest
|
||||
docker push $REPOSITORY_URI:latest
|
||||
```
|
||||
|
||||
In either case, we must build the ECR repository before deploying the lambda function. For sam, the `DeployECROnly` parameter is used to control this behavior for sam. You can set it to `false` in your [samconfig.toml](sam/samconfig.toml) after the first deploy. Thereafter you can use `--no-confirm-changeset` instead of `--guided`. For terraform, simply omit the `-target` parameter to build the full stack.
|
||||
|
||||
### Deploy the lambda function
|
||||
|
||||
With Terraform:
|
||||
|
||||
```sh
|
||||
terraform apply
|
||||
```
|
||||
|
||||
With aws-sam-cli:
|
||||
|
||||
```sh
|
||||
sam deploy --guided --profile playground --region us-west-1 --parameter-overrides DeployECROnly=false --resolve-image-repos
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
Get the function url for the lambda function
|
||||
|
||||
```sh
|
||||
export FUNCTION_NAME=
|
||||
aws lambda get-function-url-config --function-name $FUNCTION_NAME --query 'FunctionUrl' --output text
|
||||
```
|
||||
|
||||
Check the image uri for a lambda function
|
||||
|
||||
```sh
|
||||
aws lambda get-function --function-name $FUNCTION_NAME --query 'Code.ImageUri' --output text
|
||||
```
|
||||
|
||||
Review the images in an ECR repository
|
||||
|
||||
```sh
|
||||
export REPOSITORY_NAME=hello-world
|
||||
aws ecr describe-images --repository-name $REPOSITORY_NAME --region $AWS_REGION --profile $AWS_PROFILE
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
With Terraform:
|
||||
|
||||
```sh
|
||||
export FUNCTION_NAME=hello_docker
|
||||
aws logs tail /aws/lambda/$FUNCTION_NAME --since 1h --profile $AWS_PROFILE --region $AWS_REGION
|
||||
aws logs tail /aws/lambda/$FUNCTION_NAME --follow --profile $AWS_PROFILE --region $AWS_REGION
|
||||
```
|
||||
|
||||
With aws-sam-cli:
|
||||
|
||||
```sh
|
||||
sam logs -n HelloWorldFunction --profile playground --tail
|
||||
```
|
19
containers/hello-deno/Dockerfile
Normal file
19
containers/hello-deno/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# Set up the base image
|
||||
FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 AS aws-lambda-adapter
|
||||
FROM denoland/deno:bin-1.45.2 AS deno_bin
|
||||
FROM debian:bookworm-20230703-slim AS deno_runtime
|
||||
COPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter
|
||||
COPY --from=deno_bin /deno /usr/local/bin/deno
|
||||
ENV PORT=8000
|
||||
EXPOSE 8000
|
||||
RUN mkdir /var/deno_dir
|
||||
ENV DENO_DIR=/var/deno_dir
|
||||
|
||||
# Copy the function code
|
||||
WORKDIR "/var/task"
|
||||
COPY . /var/task
|
||||
|
||||
# Warmup caches
|
||||
RUN timeout 10s deno run -A main.ts || [ $? -eq 124 ] || exit 1
|
||||
|
||||
CMD ["deno", "run", "-A", "main.ts"]
|
43
containers/hello-deno/GUIDE.md
Normal file
43
containers/hello-deno/GUIDE.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Guide
|
||||
|
||||
## Deploy Lambda Container Image
|
||||
|
||||
Create the ECR (Elastic Container Registry) repository and authenticate docker to it.
|
||||
|
||||
```sh
|
||||
export AWS_REGION=us-west-1
|
||||
export AWS_PROFILE=playground
|
||||
aws configure sso --profile $AWS_PROFILE
|
||||
aws ecr create-repository --repository-name hello-world --region $AWS_REGION --profile $AWS_PROFILE
|
||||
export REPOSITORY_URI=$(aws ecr describe-repositories --repository-names hello-world --region $AWS_REGION --profile $AWS_PROFILE | jq -r '.repositories[0].repositoryUri')
|
||||
aws ecr get-login-password --region $AWS_REGION --profile $AWS_PROFILE | docker login --username AWS --password-stdin $REPOSITORY_URI
|
||||
```
|
||||
|
||||
Build the docker image and push it to the ECR repository.
|
||||
|
||||
```sh
|
||||
docker build -t hello-world .
|
||||
docker tag hello-world:latest $REPOSITORY_URI:latest
|
||||
docker push $REPOSITORY_URI:latest
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Test the container image locally.
|
||||
|
||||
```sh
|
||||
docker run -p 8000:8000 hello-world:latest
|
||||
```
|
||||
|
||||
Check it in another terminal tab.
|
||||
|
||||
```sh
|
||||
curl http://localhost:8000
|
||||
docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
Cleanup the repo.
|
||||
|
||||
```sh
|
||||
aws ecr delete-repository --repository-name hello-world --region $AWS_REGION --profile $AWS_PROFILE --force
|
||||
```
|
0
containers/hello-deno/deno.json
Normal file
0
containers/hello-deno/deno.json
Normal file
2
containers/hello-deno/main.ts
Normal file
2
containers/hello-deno/main.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// https://docs.deno.com/examples/aws_lambda_tutorial/
|
||||
Deno.serve((req) => new Response("Hello World!"));
|
27
containers/hello-openapi/Dockerfile
Normal file
27
containers/hello-openapi/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
||||
# Set up the base image
|
||||
|
||||
# https://gallery.ecr.aws/awsguru/aws-lambda-adapter
|
||||
FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4@sha256:e2653f741cd15851ba4f13f3cc47d29f2d14377c7d11737bfa272baa1b569007 AS aws-lambda-adapter
|
||||
|
||||
# https://hub.docker.com/r/denoland/deno
|
||||
# https://github.com/denoland/deno_docker
|
||||
FROM docker.io/denoland/deno:bin@sha256:49206ee7e411bba0ac80047814ce9d6ceaf4eabe36f8cde7445839b91f8f5df4 AS deno_bin
|
||||
|
||||
# https://hub.docker.com/_/debian
|
||||
FROM docker.io/library/debian:bookworm-slim@sha256:1537a6a1cbc4b4fd401da800ee9480207e7dc1f23560c21259f681db56768f63 AS deno_runtime
|
||||
|
||||
COPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter
|
||||
COPY --from=deno_bin /deno /usr/local/bin/deno
|
||||
ENV PORT=8000
|
||||
EXPOSE 8000
|
||||
RUN mkdir /var/deno_dir
|
||||
ENV DENO_DIR=/var/deno_dir
|
||||
|
||||
# Copy the function code
|
||||
WORKDIR "/var/task"
|
||||
COPY . /var/task
|
||||
|
||||
# Warmup caches
|
||||
RUN timeout 10s deno run -A main.ts || [ $? -eq 124 ] || exit 1
|
||||
|
||||
CMD ["deno", "run", "-A", "main.ts"]
|
47
containers/hello-openapi/GUIDE.md
Normal file
47
containers/hello-openapi/GUIDE.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Guide
|
||||
|
||||
Build the docker image and run it locally. Or run the deno server directly:
|
||||
|
||||
Test the server locally
|
||||
|
||||
```sh
|
||||
deno task start
|
||||
```
|
||||
|
||||
Then open [http://localhost:8000/reference](http://localhost:8000/reference) in your browser.
|
||||
|
||||
Test an authenticated endpoint:
|
||||
|
||||
```sh
|
||||
curl -H 'Authorization: Bearer hunter2' 127.0.0.1:8000/users/123
|
||||
```
|
||||
|
||||
Update the Dockerfile with the latest SHA256 digests.
|
||||
|
||||
```sh
|
||||
./update_dockerfile.sh hello-openapi
|
||||
```
|
||||
|
||||
Build the docker image.
|
||||
|
||||
```sh
|
||||
docker build -t hello-openapi .
|
||||
```
|
||||
|
||||
Test the container image locally.
|
||||
|
||||
```sh
|
||||
docker run -p 8000:8000 hello-openapi:latest
|
||||
```
|
||||
|
||||
Stop it from another terminal tab.
|
||||
|
||||
```sh
|
||||
docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
Lock the updated image digests in the Dockerfile.
|
||||
|
||||
```sh
|
||||
./lock_dockerfile.sh hello-openapi
|
||||
```
|
5
containers/hello-openapi/deno.json
Normal file
5
containers/hello-openapi/deno.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"tasks": {
|
||||
"start": "deno run --allow-net --watch main.ts"
|
||||
}
|
||||
}
|
78
containers/hello-openapi/deno.lock
Normal file
78
containers/hello-openapi/deno.lock
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:@hono/zod-openapi@*": "0.18.3_hono@4.6.13_zod@3.23.8",
|
||||
"npm:@scalar/hono-api-reference@*": "0.5.163_hono@4.6.13",
|
||||
"npm:hono@*": "4.6.13"
|
||||
},
|
||||
"npm": {
|
||||
"@asteasolutions/zod-to-openapi@7.3.0_zod@3.23.8": {
|
||||
"integrity": "sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q==",
|
||||
"dependencies": [
|
||||
"openapi3-ts",
|
||||
"zod"
|
||||
]
|
||||
},
|
||||
"@hono/zod-openapi@0.18.3_hono@4.6.13_zod@3.23.8": {
|
||||
"integrity": "sha512-bNlRDODnp7P9Fs13ZPajEOt13G0XwXKfKRHMEFCphQsFiD1Y+twzHaglpNAhNcflzR1DQwHY92ZS06b4LTPbIQ==",
|
||||
"dependencies": [
|
||||
"@asteasolutions/zod-to-openapi",
|
||||
"@hono/zod-validator",
|
||||
"hono",
|
||||
"zod"
|
||||
]
|
||||
},
|
||||
"@hono/zod-validator@0.4.1_hono@4.6.13_zod@3.23.8": {
|
||||
"integrity": "sha512-I8LyfeJfvVmC5hPjZ2Iij7RjexlgSBT7QJudZ4JvNPLxn0JQ3sqclz2zydlwISAnw21D2n4LQ0nfZdoiv9fQQA==",
|
||||
"dependencies": [
|
||||
"hono",
|
||||
"zod"
|
||||
]
|
||||
},
|
||||
"@scalar/hono-api-reference@0.5.163_hono@4.6.13": {
|
||||
"integrity": "sha512-kIZRMSIBT1Ac0PjWLuoIERZYTk0ZTp+B/zfK13lOjTQ8cgPnau34hsAT8Bydr9hOWENbmEPOumMBFJ1F81+PKw==",
|
||||
"dependencies": [
|
||||
"@scalar/types",
|
||||
"hono"
|
||||
]
|
||||
},
|
||||
"@scalar/openapi-types@0.1.5": {
|
||||
"integrity": "sha512-6geH9ehvQ/sG/xUyy3e0lyOw3BaY5s6nn22wHjEJhcobdmWyFER0O6m7AU0ZN4QTjle/gYvFJOjj552l/rsNSw=="
|
||||
},
|
||||
"@scalar/types@0.0.23": {
|
||||
"integrity": "sha512-dOvQig4hyeVw1kXIo9MQAnM9tUt9vCOZs3zOe6oSqOUG8xY7+WXioirlRCsc+wcQegMbuNYOlNBXCDugOP1YJA==",
|
||||
"dependencies": [
|
||||
"@scalar/openapi-types",
|
||||
"@unhead/schema"
|
||||
]
|
||||
},
|
||||
"@unhead/schema@1.11.13": {
|
||||
"integrity": "sha512-fIpQx6GCpl99l4qJXsPqkXxO7suMccuLADbhaMSkeXnVEi4ZIle+l+Ri0z+GHAEpJj17FMaQdO5n9FMSOMUxkw==",
|
||||
"dependencies": [
|
||||
"hookable",
|
||||
"zhead"
|
||||
]
|
||||
},
|
||||
"hono@4.6.13": {
|
||||
"integrity": "sha512-haV0gaMdSjy9URCRN9hxBPlqHa7fMm/T72kAImIxvw4eQLbNz1rgjN4hHElLJSieDiNuiIAXC//cC6YGz2KCbg=="
|
||||
},
|
||||
"hookable@5.5.3": {
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
||||
},
|
||||
"openapi3-ts@4.4.0": {
|
||||
"integrity": "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==",
|
||||
"dependencies": [
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
"yaml@2.6.1": {
|
||||
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg=="
|
||||
},
|
||||
"zhead@2.2.4": {
|
||||
"integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="
|
||||
},
|
||||
"zod@3.23.8": {
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="
|
||||
}
|
||||
}
|
||||
}
|
29
containers/hello-openapi/lock_dockerfile.sh
Executable file
29
containers/hello-openapi/lock_dockerfile.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Function to get SHA256 digest
|
||||
get_digest() {
|
||||
docker pull $1 > /dev/null
|
||||
digest=$(docker inspect --format='{{index .RepoDigests 0}}' $1 | cut -d'@' -f2)
|
||||
echo $digest
|
||||
}
|
||||
|
||||
# Array of images to process
|
||||
declare -A images
|
||||
images["public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4"]="aws-lambda-adapter"
|
||||
images["docker.io/denoland/deno:bin"]="deno_bin"
|
||||
images["docker.io/library/debian:bookworm-slim"]="deno_runtime"
|
||||
|
||||
# Process each image
|
||||
for image in "${!images[@]}"; do
|
||||
alias=${images[$image]}
|
||||
digest=$(get_digest $image)
|
||||
echo "Image: $image"
|
||||
echo "Alias: $alias"
|
||||
echo "Digest: $digest"
|
||||
echo
|
||||
|
||||
# Update Dockerfile
|
||||
sed -i "s|FROM $image AS $alias|FROM $image@$digest AS $alias|g" Dockerfile
|
||||
done
|
||||
|
||||
echo "Dockerfile updated with SHA256 digests."
|
107
containers/hello-openapi/main.ts
Normal file
107
containers/hello-openapi/main.ts
Normal file
@ -0,0 +1,107 @@
|
||||
/* Define our schemas */
|
||||
|
||||
// fun fact: the @ means zod-openapi is a scoped package instead of a submodule of hono
|
||||
import { z } from "npm:@hono/zod-openapi";
|
||||
import { bearerAuth } from "npm:hono/bearer-auth";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
id: z
|
||||
.string()
|
||||
.min(3)
|
||||
.openapi({
|
||||
param: {
|
||||
name: "id",
|
||||
in: "path",
|
||||
},
|
||||
example: "1212121",
|
||||
}),
|
||||
});
|
||||
|
||||
const UserSchema = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
example: "123",
|
||||
}),
|
||||
name: z.string().openapi({
|
||||
example: "John Doe",
|
||||
}),
|
||||
age: z.number().openapi({
|
||||
example: 42,
|
||||
}),
|
||||
})
|
||||
.openapi("User");
|
||||
|
||||
/* Create a route */
|
||||
|
||||
import { createRoute } from "npm:@hono/zod-openapi";
|
||||
|
||||
const route = createRoute({
|
||||
method: "get",
|
||||
security: [
|
||||
{
|
||||
Bearer: [],
|
||||
},
|
||||
],
|
||||
path: "/users/{id}",
|
||||
request: {
|
||||
params: ParamsSchema,
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: UserSchema,
|
||||
},
|
||||
},
|
||||
description: "Retrieve the user",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/* App setup */
|
||||
|
||||
import { OpenAPIHono } from "npm:@hono/zod-openapi";
|
||||
|
||||
const app = new OpenAPIHono();
|
||||
|
||||
// https://github.com/honojs/middleware/tree/main/packages/zod-openapi#how-to-setup-authorization
|
||||
// https://hono.dev/docs/middleware/builtin/bearer-auth
|
||||
const token = "hunter2";
|
||||
app.use("/users/*", bearerAuth({ token }));
|
||||
app.openAPIRegistry.registerComponent("securitySchemes", "Bearer", {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
});
|
||||
|
||||
app.openapi(route, (c) => {
|
||||
const { id } = c.req.valid("param");
|
||||
return c.json({
|
||||
id,
|
||||
age: 21,
|
||||
name: "pleb",
|
||||
});
|
||||
});
|
||||
|
||||
// OpenAPI Spec
|
||||
app.doc("/openapi.json", {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
version: "1.0.0",
|
||||
title: "My API",
|
||||
},
|
||||
});
|
||||
|
||||
/* OpenAPI Reference */
|
||||
|
||||
import { apiReference } from "npm:@scalar/hono-api-reference";
|
||||
|
||||
app.get(
|
||||
"/reference",
|
||||
apiReference({
|
||||
pageTitle: "Scalar/Hono API Reference",
|
||||
theme: "elysiajs",
|
||||
spec: { url: "/openapi.json" },
|
||||
}),
|
||||
);
|
||||
|
||||
Deno.serve(app.fetch);
|
30
containers/hello-openapi/update_dockerfile.sh
Normal file
30
containers/hello-openapi/update_dockerfile.sh
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to get latest SHA256 digest
|
||||
get_latest_digest() {
|
||||
image_name=$(echo $1 | cut -d'@' -f1)
|
||||
docker pull $image_name > /dev/null
|
||||
digest=$(docker inspect --format='{{index .RepoDigests 0}}' $image_name | cut -d'@' -f2)
|
||||
echo $digest
|
||||
}
|
||||
|
||||
# Update Dockerfile
|
||||
update_dockerfile() {
|
||||
old_line=$1
|
||||
new_digest=$2
|
||||
new_line=$(echo $old_line | sed "s|@sha256:[a-f0-9]*|@$new_digest|")
|
||||
sed -i "s|$old_line|$new_line|" Dockerfile
|
||||
echo "Updated: $new_line"
|
||||
}
|
||||
|
||||
# Process Dockerfile
|
||||
while IFS= read -r line
|
||||
do
|
||||
if [[ $line == FROM* && $line == *@sha256:* ]]; then
|
||||
image=$(echo $line | awk '{print $2}')
|
||||
new_digest=$(get_latest_digest $image)
|
||||
update_dockerfile "$line" "$new_digest"
|
||||
fi
|
||||
done < Dockerfile
|
||||
|
||||
echo "Dockerfile updated with latest SHA256 digests."
|
14
notes/decisions.md
Normal file
14
notes/decisions.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Architecture Decision Record
|
||||
|
||||
A powerful stack for building APIs that is flexible, performant, and easy to reason about.
|
||||
|
||||
* Cloud: AWS - status quo for QOS (we're not locked into it)
|
||||
* CI/CD: Gitlab - multi-cloud, versatile, FOSS (ENA is already using it)
|
||||
* Deployment: Terraform - declarative, IaC, multi-cloud
|
||||
* Containers: Docker - consistency between environments, rootless in dev
|
||||
* TypeScript: type-safety, anyone with JS experience can contribute (ServiceNow uses JS)
|
||||
* Runtime: [Deno](https://deno.com/) - secure-by-default, URL-based module resolution, dependency locking, built-in toolchain
|
||||
* API Layer: [hono](https://hono.dev/) - portable, fast, simple, automatic OpenAPI w/ [@hono/zod-openapi](https://hono.dev/examples/zod-openapi)
|
||||
* OpenAPI UI: [scalar](https://scalar.com/)/[hono-api-reference](https://github.com/scalar/scalar/blob/main/packages/hono-api-reference/README.md) - offline-first, code generation
|
||||
|
||||
The strong alternative would be Python with FastAPI, which might be more suitable for smaller projects.
|
52
notes/docker.md
Normal file
52
notes/docker.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Build Docker Image
|
||||
|
||||
Requirements:
|
||||
|
||||
* sam, aws-cli, docker
|
||||
|
||||
## Docker Container
|
||||
|
||||
Push a docker image to the ECR repository.
|
||||
|
||||
```sh
|
||||
export AWS_REGION=us-west-1
|
||||
export AWS_PROFILE=playground
|
||||
aws configure sso --profile $AWS_PROFILE
|
||||
export REPOSITORY_URI=$(aws ecr describe-repositories --repository-names hello-world --region $AWS_REGION --profile $AWS_PROFILE | jq -r '.repositories[0].repositoryUri')
|
||||
aws ecr get-login-password --region $AWS_REGION --profile $AWS_PROFILE | docker login --username AWS --password-stdin $REPOSITORY_URI
|
||||
```
|
||||
|
||||
Build the docker image and push it to the ECR repository.
|
||||
|
||||
```sh
|
||||
docker build -t hello-world .
|
||||
docker tag hello-world:latest $REPOSITORY_URI:latest
|
||||
docker push $REPOSITORY_URI:latest
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Test the container image locally.
|
||||
|
||||
```sh
|
||||
docker run -p 8000:8000 hello-world:latest
|
||||
```
|
||||
|
||||
Check it in another terminal tab.
|
||||
|
||||
```sh
|
||||
curl http://localhost:8000
|
||||
docker stop $(docker ps -q)
|
||||
```
|
||||
|
||||
Cleanup the repo.
|
||||
|
||||
```sh
|
||||
aws ecr delete-repository --repository-name hello-world --region $AWS_REGION --profile $AWS_PROFILE --force
|
||||
```
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [deno lambda guide](https://docs.deno.com/runtime/tutorials/aws_lambda/)
|
||||
* (Deprecated) Deno on AWS Lambda: [Manual lambda packaging guide](https://github.com/denoland/deno-lambda/tree/f735a1a4f0a4a57a348f8af88d6cf30ddc0a5f2e)
|
7
notes/hello-hono/README.md
Normal file
7
notes/hello-hono/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
```
|
||||
deno task start
|
||||
```
|
||||
|
||||
```
|
||||
curl 'localhost:8000/posts/9?page=7'
|
||||
```
|
12
notes/hello-hono/deno.json
Normal file
12
notes/hello-hono/deno.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"imports": {
|
||||
"hono": "jsr:@hono/hono@^4.6.13"
|
||||
},
|
||||
"tasks": {
|
||||
"start": "deno run --allow-net --watch main.ts"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"jsx": "precompile",
|
||||
"jsxImportSource": "hono/jsx"
|
||||
}
|
||||
}
|
16
notes/hello-hono/deno.lock
Normal file
16
notes/hello-hono/deno.lock
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@hono/hono@^4.6.13": "4.6.13"
|
||||
},
|
||||
"jsr": {
|
||||
"@hono/hono@4.6.13": {
|
||||
"integrity": "198970fc6facf71a88ee57aa1ed32143b56bcb0f842fa965ca3ee7d6fdd21a8e"
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@hono/hono@^4.6.13"
|
||||
]
|
||||
}
|
||||
}
|
83
notes/hello-hono/main.ts
Normal file
83
notes/hello-hono/main.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
// return text
|
||||
app.get('/', (c) => {
|
||||
return c.text('Hello Hono!')
|
||||
})
|
||||
|
||||
// return JSON
|
||||
app.get('/json', (c) => {
|
||||
return c.json({
|
||||
ok: true,
|
||||
message: 'Hello Hono!',
|
||||
})
|
||||
})
|
||||
|
||||
// query params: curl 'localhost:8000/posts/9?page=7'
|
||||
app.get('/posts/:id', (c) => {
|
||||
const page = c.req.query('page')
|
||||
const id = c.req.param('id')
|
||||
c.header('X-Message', 'Hi!')
|
||||
return c.text(`You want to see ${page} of ${id}`)
|
||||
})
|
||||
|
||||
// create: curl -X POST localhost:8000/posts
|
||||
app.post('/posts', (c) => c.text('Created!', 201))
|
||||
|
||||
// delete: curl -X DELETE localhost:8000/posts/8
|
||||
app.delete('/posts/:id', (c) =>
|
||||
c.text(`${c.req.param('id')} should be deleted!`)
|
||||
)
|
||||
|
||||
// return raw response
|
||||
app.get('/raw', () => {
|
||||
return new Response('Hello Hono!')
|
||||
})
|
||||
|
||||
/*
|
||||
// return HTML using JSX (Deno's linter hates it)
|
||||
const View = () => {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello Hono!</h1>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
app.get('/page', (c) => {
|
||||
return c.html(<View />)
|
||||
})
|
||||
*/
|
||||
|
||||
// "Middleware can do the hard work for you. For example, add in Basic Authentication."
|
||||
// curl -u admin:secret http://localhost:8000/admin
|
||||
import { basicAuth } from 'hono/basic-auth'
|
||||
app.use(
|
||||
'/admin/*',
|
||||
basicAuth({
|
||||
username: 'admin',
|
||||
password: 'secret',
|
||||
})
|
||||
)
|
||||
app.get('/admin', (c) => {
|
||||
return c.text('You are authorized!')
|
||||
})
|
||||
|
||||
// "There are Adapters for platform-dependent functions, e.g., handling static files or WebSocket.
|
||||
// For example, to handle WebSocket in Cloudflare Workers, import hono/cloudflare-workers."
|
||||
/*
|
||||
import { upgradeWebSocket } from 'hono/cloudflare-workers'
|
||||
|
||||
app.get(
|
||||
'/ws',
|
||||
upgradeWebSocket((c) => {
|
||||
// ...
|
||||
})
|
||||
)
|
||||
*/
|
||||
|
||||
Deno.serve(app.fetch)
|
BIN
notes/ide-deno-extension.png
Normal file
BIN
notes/ide-deno-extension.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
12
sam/samconfig.toml
Normal file
12
sam/samconfig.toml
Normal file
@ -0,0 +1,12 @@
|
||||
version = 0.1
|
||||
[default.deploy.parameters]
|
||||
stack_name = "hello-world"
|
||||
resolve_s3 = true
|
||||
s3_prefix = "hello-world"
|
||||
region = "us-west-1"
|
||||
profile = "playground"
|
||||
confirm_changeset = true
|
||||
capabilities = "CAPABILITY_IAM"
|
||||
parameter_overrides = "DeployECROnly=\"false\""
|
||||
image_repositories = []
|
||||
resolve_image_repos = true
|
96
sam/template.yaml
Normal file
96
sam/template.yaml
Normal file
@ -0,0 +1,96 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Transform: AWS::Serverless-2016-10-31
|
||||
Description: Docker Container Lambda Function
|
||||
|
||||
Globals:
|
||||
Function:
|
||||
Timeout: 10
|
||||
|
||||
Parameters:
|
||||
DeployECROnly:
|
||||
Type: String
|
||||
Default: "false"
|
||||
AllowedValues: ["true", "false"]
|
||||
Description: If true, only deploys the ECR repository
|
||||
|
||||
Conditions:
|
||||
DeployFullStack: !Equals [!Ref DeployECROnly, "false"]
|
||||
|
||||
Resources:
|
||||
|
||||
HelloWorldRepository:
|
||||
Type: AWS::ECR::Repository
|
||||
Properties:
|
||||
RepositoryName: hello-world
|
||||
ImageScanningConfiguration:
|
||||
ScanOnPush: true
|
||||
|
||||
HelloWorldFunctionRole:
|
||||
Condition: DeployFullStack
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: "Allow"
|
||||
Principal:
|
||||
Service:
|
||||
- "lambda.amazonaws.com"
|
||||
Action:
|
||||
- "sts:AssumeRole"
|
||||
Policies:
|
||||
- PolicyName: HelloWorldFunctionAccess
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: "Allow"
|
||||
Action:
|
||||
- "secretsmanager:GetSecretValue"
|
||||
Resource:
|
||||
- "arn:aws:secretsmanager:us-west-1:003525187774:secret:*"
|
||||
ManagedPolicyArns:
|
||||
# - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
|
||||
- arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs
|
||||
|
||||
HelloWorldFunction:
|
||||
Condition: DeployFullStack
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
PackageType: Image
|
||||
ImageUri: !Sub "${HelloWorldRepository.RepositoryUri}:latest"
|
||||
# ImageUri: !Sub "${HelloWorldRepository.RepositoryUri}@sha256:c195f0c7d0bb2a5bdf408f149f4cf558e3376128a4887b5cd0a6fb2196992bf3"
|
||||
Role: !GetAtt HelloWorldFunctionRole.Arn
|
||||
Environment:
|
||||
Variables:
|
||||
PORT: 8000
|
||||
Architectures:
|
||||
- x86_64
|
||||
FunctionUrlConfig:
|
||||
AuthType: NONE
|
||||
Cors:
|
||||
AllowOrigins:
|
||||
- '*'
|
||||
|
||||
HelloWorldFunctionPermission:
|
||||
Condition: DeployFullStack
|
||||
Type: AWS::Lambda::Permission
|
||||
Properties:
|
||||
FunctionName: !Ref HelloWorldFunction
|
||||
Action: lambda:InvokeFunctionUrl
|
||||
Principal: '*'
|
||||
FunctionUrlAuthType: NONE
|
||||
|
||||
Outputs:
|
||||
HelloWorldRepositoryUri:
|
||||
Description: ECR Repository URI
|
||||
Value: !GetAtt HelloWorldRepository.RepositoryUri
|
||||
HelloWorldFunctionName:
|
||||
Condition: DeployFullStack
|
||||
Description: Lambda Function Name
|
||||
Value: !Ref HelloWorldFunction
|
||||
|
||||
# Error: Requested attribute FunctionUrl does not exist in schema for AWS::Lambda::Function
|
||||
# HelloWorldFunctionUrl:
|
||||
# Condition: DeployFullStack
|
||||
# Description: "The URL of the Lambda Function"
|
||||
# Value: !GetAtt HelloWorldFunction.FunctionUrl
|
25
terraform/.terraform.lock.hcl
Normal file
25
terraform/.terraform.lock.hcl
Normal file
@ -0,0 +1,25 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.80.0"
|
||||
constraints = "~> 5.0"
|
||||
hashes = [
|
||||
"h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=",
|
||||
"zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df",
|
||||
"zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807",
|
||||
"zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121",
|
||||
"zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926",
|
||||
"zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf",
|
||||
"zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc",
|
||||
"zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9",
|
||||
"zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1",
|
||||
"zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11",
|
||||
"zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4",
|
||||
"zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b",
|
||||
"zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac",
|
||||
"zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb",
|
||||
]
|
||||
}
|
84
terraform/main.tf
Normal file
84
terraform/main.tf
Normal file
@ -0,0 +1,84 @@
|
||||
provider "aws" {
|
||||
region = "us-west-1"
|
||||
profile = "playground"
|
||||
}
|
||||
|
||||
# ECR Repository
|
||||
resource "aws_ecr_repository" "hello_docker" {
|
||||
name = "hello-docker"
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
}
|
||||
|
||||
# IAM Role for Lambda
|
||||
resource "aws_iam_role" "hello_docker_role" {
|
||||
name = "hello_docker_lambda_role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Action = "sts:AssumeRole"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "lambda.amazonaws.com"
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# IAM Policy for Secrets Manager access
|
||||
resource "aws_iam_role_policy" "hello_docker_policy" {
|
||||
name = "hello_docker_function_access"
|
||||
role = aws_iam_role.hello_docker_role.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = ["secretsmanager:GetSecretValue"]
|
||||
Resource = ["arn:aws:secretsmanager:us-west-1:003525187774:secret:*"]
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# Attach CloudWatch Logs policy
|
||||
resource "aws_iam_role_policy_attachment" "hello_docker_logs" {
|
||||
role = aws_iam_role.hello_docker_role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs"
|
||||
}
|
||||
|
||||
# Lambda Function
|
||||
resource "aws_lambda_function" "hello_docker" {
|
||||
function_name = "hello-docker"
|
||||
role = aws_iam_role.hello_docker_role.arn
|
||||
package_type = "Image"
|
||||
image_uri = "${aws_ecr_repository.hello_docker.repository_url}:latest"
|
||||
architectures = ["x86_64"]
|
||||
timeout = 10
|
||||
|
||||
environment {
|
||||
variables = {
|
||||
PORT = "8000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Lambda Function URL
|
||||
resource "aws_lambda_function_url" "hello_docker_url" {
|
||||
function_name = aws_lambda_function.hello_docker.function_name
|
||||
authorization_type = "NONE"
|
||||
|
||||
cors {
|
||||
allow_origins = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
# Lambda permission for Function URL
|
||||
resource "aws_lambda_permission" "function_url" {
|
||||
statement_id = "AllowExecutionFromFunctionURL"
|
||||
action = "lambda:InvokeFunctionUrl"
|
||||
function_name = aws_lambda_function.hello_docker.function_name
|
||||
principal = "*"
|
||||
function_url_auth_type = "NONE"
|
||||
}
|
9
terraform/outputs.tf
Normal file
9
terraform/outputs.tf
Normal file
@ -0,0 +1,9 @@
|
||||
output "function_url" {
|
||||
description = "Lambda Function URL"
|
||||
value = aws_lambda_function_url.hello_docker_url.function_url
|
||||
}
|
||||
|
||||
output "ecr_repository_url" {
|
||||
description = "ECR Repository URI"
|
||||
value = aws_ecr_repository.hello_docker.repository_url
|
||||
}
|
9
terraform/versions.tf
Normal file
9
terraform/versions.tf
Normal file
@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
}
|
||||
required_version = ">= 1.0"
|
||||
}
|
Loading…
Reference in New Issue
Block a user