From bc200222ab6468609856cc6bf6463923cdbeb198 Mon Sep 17 00:00:00 2001
From: Kohei MATSUSHITA <ma2shita+git@ma2shita.jp>
Date: Tue, 30 Jan 2024 21:19:22 +0900
Subject: [PATCH] Add Sinatra example

---
 examples/sinatra/.gitignore           | 380 ++++++++++++++++++++++++++
 examples/sinatra/README.md            |  76 ++++++
 examples/sinatra/app/Dockerfile       |   6 +
 examples/sinatra/app/src/Gemfile      |   9 +
 examples/sinatra/app/src/Gemfile.lock |  35 +++
 examples/sinatra/app/src/app.rb       |  18 ++
 examples/sinatra/template.yml         |  32 +++
 7 files changed, 556 insertions(+)
 create mode 100644 examples/sinatra/.gitignore
 create mode 100644 examples/sinatra/README.md
 create mode 100644 examples/sinatra/app/Dockerfile
 create mode 100644 examples/sinatra/app/src/Gemfile
 create mode 100644 examples/sinatra/app/src/Gemfile.lock
 create mode 100644 examples/sinatra/app/src/app.rb
 create mode 100644 examples/sinatra/template.yml

diff --git a/examples/sinatra/.gitignore b/examples/sinatra/.gitignore
new file mode 100644
index 00000000..42ddf2cb
--- /dev/null
+++ b/examples/sinatra/.gitignore
@@ -0,0 +1,380 @@
+# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=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 ###
+# General
+.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, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+### 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/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# 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/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# 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/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode
+
+.aws-sam
diff --git a/examples/sinatra/README.md b/examples/sinatra/README.md
new file mode 100644
index 00000000..52b9dbcd
--- /dev/null
+++ b/examples/sinatra/README.md
@@ -0,0 +1,76 @@
+# Sinatra example
+A basic example of a Sinatra application.
+
+Using AWS Lambda Adapter, You can package this web application into Docker image, push to ECR, and deploy to Lambda, ECS/EKS, or EC2.
+
+The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yaml` file in the root folder contains the application definition.
+
+The top level folder is a typical AWS SAM project. The `app` directory is a Sinatra application with a Dockerfile.
+
+```dockerfile
+FROM public.ecr.aws/docker/library/ruby:3.3
+COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.1 /lambda-adapter /opt/extensions/lambda-adapter
+WORKDIR /var/task
+COPY Gemfile Gemfile.lock ./
+RUN bundle install
+COPY . .
+CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0", "-p", "8080"]
+```
+
+## Pre-requisites
+
+The following tools should be installed and configured.
+* [AWS CLI](https://aws.amazon.com/cli/)
+* [SAM CLI](https://github.com/awslabs/aws-sam-cli)
+* [Docker](https://www.docker.com/products/docker-desktop)
+* [Ruby](https://www.ruby-lang.org/)
+
+## Deploy to Lambda
+
+Navigate to the sample's folder and use the SAM CLI to build a container image:
+
+```shell
+$ sam build
+```
+
+This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory.
+
+To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen:
+
+```shell
+$ sam deploy --guided
+```
+
+Please take note of the container image name.
+Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL.
+
+NOTE: This SAM does not use Amazon API Gateway, but uses AWS Lambda function URLs to create HTTP endpoints.
+
+```shell
+...
+CloudFormation outputs from deployed stack
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+Outputs
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+Key                 SinatraFunction1
+Description         Function URL endpoint on AWS Lambda
+Value               https://xxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.REGION.on.aws/
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------...
+
+$ curl https://xxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.REGION.on.aws/
+```
+
+## Run the docker locally
+
+We can run the same docker image locally, so that we know it can be deployed to ECS Fargate and EKS EC2 without code changes.
+
+```shell
+$ docker run -it --rm -p 8080:8080 {ECR Image}
+```
+
+Use curl from another session to checking that the Docker container is working.
+
+```shell
+$ curl localhost:8080/
+#=> Hello! I am <b>Sinatra</b>.
+```
diff --git a/examples/sinatra/app/Dockerfile b/examples/sinatra/app/Dockerfile
new file mode 100644
index 00000000..e70e5e95
--- /dev/null
+++ b/examples/sinatra/app/Dockerfile
@@ -0,0 +1,6 @@
+FROM public.ecr.aws/docker/library/ruby:3.3
+COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.1 /lambda-adapter /opt/extensions/lambda-adapter
+WORKDIR /var/task
+COPY ./src ./
+RUN bundle install
+CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0", "-p", "8080"]
diff --git a/examples/sinatra/app/src/Gemfile b/examples/sinatra/app/src/Gemfile
new file mode 100644
index 00000000..3161e9b1
--- /dev/null
+++ b/examples/sinatra/app/src/Gemfile
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+# gem "rails"
+
+gem "sinatra", "~> 4.0"
+
+gem "rackup", "~> 2.1"
diff --git a/examples/sinatra/app/src/Gemfile.lock b/examples/sinatra/app/src/Gemfile.lock
new file mode 100644
index 00000000..8dc12218
--- /dev/null
+++ b/examples/sinatra/app/src/Gemfile.lock
@@ -0,0 +1,35 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    base64 (0.2.0)
+    mustermann (3.0.0)
+      ruby2_keywords (~> 0.0.1)
+    rack (3.0.8)
+    rack-protection (4.0.0)
+      base64 (>= 0.1.0)
+      rack (>= 3.0.0, < 4)
+    rack-session (2.0.0)
+      rack (>= 3.0.0)
+    rackup (2.1.0)
+      rack (>= 3)
+      webrick (~> 1.8)
+    ruby2_keywords (0.0.5)
+    sinatra (4.0.0)
+      mustermann (~> 3.0)
+      rack (>= 3.0.0, < 4)
+      rack-protection (= 4.0.0)
+      rack-session (>= 2.0.0, < 3)
+      tilt (~> 2.0)
+    tilt (2.3.0)
+    webrick (1.8.1)
+
+PLATFORMS
+  ruby
+  x86_64-linux
+
+DEPENDENCIES
+  rackup (~> 2.1)
+  sinatra (~> 4.0)
+
+BUNDLED WITH
+   2.5.3
diff --git a/examples/sinatra/app/src/app.rb b/examples/sinatra/app/src/app.rb
new file mode 100644
index 00000000..ad582eca
--- /dev/null
+++ b/examples/sinatra/app/src/app.rb
@@ -0,0 +1,18 @@
+require 'sinatra'
+# NOTE: logger.info => STDERR or CloudWatch Logs
+
+get '/' do
+  logger.info params # QueryString
+  'Hello! I am <b>Sinatra</b>.'
+end
+
+post '/' do
+  logger.info params # QueryString and/or x-www-form-urlencoded
+  logger.info request.body.read # e.g. application/json
+  'Post data recived.'
+end
+
+post '/events' do
+  logger.info request.body.read # Non-HTTP trigger's event
+  'success.'
+end
diff --git a/examples/sinatra/template.yml b/examples/sinatra/template.yml
new file mode 100644
index 00000000..6e829c8e
--- /dev/null
+++ b/examples/sinatra/template.yml
@@ -0,0 +1,32 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: >
+  Ruby 3.3.0
+
+  Sample SAM Template for Sinatra app.
+
+Globals:
+  Function:
+    Timeout: 60
+
+Resources:
+  SinatraFunction1:
+    Type: AWS::Serverless::Function
+    Properties:
+      PackageType: Image
+      MemorySize: 512
+      Architectures:
+        - x86_64
+      Timeout: 30
+      FunctionUrlConfig:
+        AuthType: NONE
+    Metadata:
+      DockerTag: latest
+      DockerContext: ./app
+      Dockerfile: Dockerfile
+
+Outputs:
+  SinatraFunction1:
+    Description: "Function URL endpoint on AWS Lambda"
+    Value: !GetAtt SinatraFunction1Url.FunctionUrl
+
