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