├── docs ├── demo │ ├── README.md │ ├── docs │ │ ├── __init__.py │ │ ├── wikis │ │ │ └── wiki.md │ │ └── config.py │ ├── infra │ │ ├── __init__.py │ │ ├── scripts │ │ │ ├── __init__.py │ │ │ └── validate_todo.py │ │ ├── stacks │ │ │ ├── __init__.py │ │ │ ├── lambda_stack.py │ │ │ ├── dev_stack.py │ │ │ └── prod_stack.py │ │ ├── stages │ │ │ ├── __init__.py │ │ │ └── deploy.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── layers.py │ │ │ ├── sns.py │ │ │ ├── aws_lambda.py │ │ │ └── api_gateway.py │ ├── layers │ │ ├── __init__.py │ │ └── my_custom_layer │ │ │ ├── __init__.py │ │ │ └── my_custom_layer.py │ ├── authorizers │ │ ├── __init__.py │ │ └── secret │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ ├── main.py │ │ │ └── unit.py │ ├── functions │ │ ├── custom │ │ │ ├── __init__.py │ │ │ ├── unit.py │ │ │ ├── integration.py │ │ │ ├── main.py │ │ │ └── config.py │ │ ├── external │ │ │ ├── __init__.py │ │ │ ├── unit.py │ │ │ ├── integration.py │ │ │ ├── config.py │ │ │ └── main.py │ │ ├── hello_world │ │ │ ├── __init__.py │ │ │ ├── unit.py │ │ │ ├── integration.py │ │ │ ├── main.py │ │ │ └── config.py │ │ └── private │ │ │ ├── __init__.py │ │ │ ├── unit.py │ │ │ ├── main.py │ │ │ ├── config.py │ │ │ └── integration.py │ ├── diagram.png │ ├── .coveragerc │ ├── pytest.ini │ ├── app.py │ └── requirements.txt ├── examples │ ├── README.md │ ├── docs │ │ ├── __init__.py │ │ └── config.py │ ├── infra │ │ ├── __init__.py │ │ ├── stacks │ │ │ ├── __init__.py │ │ │ ├── dev_stack.py │ │ │ ├── staging_stack.py │ │ │ └── prod_stack.py │ │ ├── stages │ │ │ ├── __init__.py │ │ │ └── deploy.py │ │ └── services │ │ │ ├── kms.py │ │ │ ├── cognito.py │ │ │ ├── websockets.py │ │ │ ├── secrets_manager.py │ │ │ ├── __init__.py │ │ │ ├── s3.py │ │ │ ├── aws_lambda.py │ │ │ ├── layers.py │ │ │ ├── api_gateway.py │ │ │ └── dynamodb.py │ ├── layers │ │ ├── __init__.py │ │ └── sm_utils │ │ │ ├── __init__.py │ │ │ └── sm_utils.py │ ├── authorizers │ │ ├── __init__.py │ │ ├── jwt │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ └── main.py │ │ └── sso │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ └── main.py │ ├── functions │ │ ├── auth │ │ │ ├── hello │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── config.py │ │ │ ├── signin │ │ │ │ ├── __init__.py │ │ │ │ └── config.py │ │ │ └── signup │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ ├── blog │ │ │ ├── feed │ │ │ │ ├── __init__.py │ │ │ │ └── config.py │ │ │ ├── comment_post │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ ├── create_post │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ ├── delete_post │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── config.py │ │ │ ├── like_post │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ ├── update_post │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ └── delete_comment │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ ├── chat │ │ │ ├── connect │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ ├── send_message │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── config.py │ │ │ └── send_connection_id │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── config.py │ │ ├── images │ │ │ ├── mailer │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── template.html │ │ │ └── qrcode │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ ├── urls │ │ │ ├── redirect │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ │ └── shortener │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ └── main.py │ │ └── guess_the_number │ │ │ ├── make_guess │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ └── main.py │ │ │ └── create_game │ │ │ ├── __init__.py │ │ │ ├── config.py │ │ │ └── main.py │ ├── diagram.png │ ├── .coveragerc │ ├── pytest.ini │ ├── app.py │ └── requirements.txt ├── docs │ ├── CNAME │ ├── index.md │ ├── assets │ │ ├── logo.png │ │ ├── favicon.png │ │ └── lambda-forge.png │ ├── images │ │ ├── logo.png │ │ ├── favicon.png │ │ └── lambda-forge.png │ ├── examples │ │ ├── blog.md │ │ ├── images │ │ │ ├── auth.png │ │ │ ├── email.png │ │ │ ├── qrcode.png │ │ │ ├── scraper.png │ │ │ ├── sender.png │ │ │ ├── receiver.png │ │ │ ├── chat-diagram.png │ │ │ ├── pass-exposed.png │ │ │ ├── pre-connect.png │ │ │ ├── prod-scraper.png │ │ │ ├── qrcode-diagram.png │ │ │ ├── sender-connect.png │ │ │ ├── three_pipelines.png │ │ │ ├── url-shortener.png │ │ │ ├── guess-the-number.png │ │ │ ├── receiver-connect.png │ │ │ ├── lambda-forge-short-url.png │ │ │ └── three_example_pipelines.png │ │ └── introduction.md │ ├── home │ │ └── images │ │ │ ├── sns-live.gif │ │ │ ├── default-dev.png │ │ │ ├── live-server.gif │ │ │ ├── live-server.png │ │ │ ├── sns-trigger.gif │ │ │ ├── updated-dev.png │ │ │ ├── dev-helloworld.png │ │ │ ├── live-trigger.png │ │ │ ├── prod-success.png │ │ │ ├── live-apigateway.gif │ │ │ ├── prod-hello-world.png │ │ │ ├── staging-success.png │ │ │ ├── integration-failed.png │ │ │ ├── integrations-failed.png │ │ │ ├── only-dev-pipeline.png │ │ │ ├── only-dev-triggers.png │ │ │ ├── staging-hello-world.png │ │ │ ├── success-pipelines.png │ │ │ ├── deployed-hello-worlds.png │ │ │ ├── prod-pipeline-running.png │ │ │ ├── staging-running-only.png │ │ │ └── staging-hello-world-api-gateway-trigger.png │ ├── theme_override_home │ │ ├── .icons │ │ │ ├── logo.png │ │ │ ├── puzzle.png │ │ │ ├── favicon.png │ │ │ └── lambda-forge.png │ │ └── main.html │ └── license │ │ └── license.md └── site │ ├── CNAME │ ├── assets │ ├── javascripts │ │ └── lunr │ │ │ └── min │ │ │ ├── lunr.jp.min.js │ │ │ ├── lunr.vi.min.js │ │ │ ├── lunr.multi.min.js │ │ │ ├── lunr.th.min.js │ │ │ ├── lunr.hy.min.js │ │ │ ├── lunr.te.min.js │ │ │ ├── lunr.ta.min.js │ │ │ └── lunr.zh.min.js │ ├── logo.png │ ├── favicon.png │ ├── images │ │ └── favicon.png │ └── lambda-forge.png │ ├── images │ ├── logo.png │ ├── favicon.png │ └── lambda-forge.png │ ├── sitemap.xml.gz │ ├── examples │ └── images │ │ ├── auth.png │ │ ├── email.png │ │ ├── qrcode.png │ │ ├── scraper.png │ │ ├── sender.png │ │ ├── receiver.png │ │ ├── chat-diagram.png │ │ ├── pass-exposed.png │ │ ├── pre-connect.png │ │ ├── prod-scraper.png │ │ ├── qrcode-diagram.png │ │ ├── sender-connect.png │ │ ├── three_pipelines.png │ │ ├── url-shortener.png │ │ ├── guess-the-number.png │ │ ├── receiver-connect.png │ │ ├── lambda-forge-short-url.png │ │ └── three_example_pipelines.png │ ├── home │ └── images │ │ ├── sns-live.gif │ │ ├── default-dev.png │ │ ├── live-server.gif │ │ ├── live-server.png │ │ ├── sns-trigger.gif │ │ ├── updated-dev.png │ │ ├── dev-helloworld.png │ │ ├── live-trigger.png │ │ ├── prod-success.png │ │ ├── live-apigateway.gif │ │ ├── prod-hello-world.png │ │ ├── staging-success.png │ │ ├── integration-failed.png │ │ ├── integrations-failed.png │ │ ├── only-dev-pipeline.png │ │ ├── only-dev-triggers.png │ │ ├── staging-hello-world.png │ │ ├── success-pipelines.png │ │ ├── deployed-hello-worlds.png │ │ ├── prod-pipeline-running.png │ │ ├── staging-running-only.png │ │ └── staging-hello-world-api-gateway-trigger.png │ ├── sitemap.xml │ └── theme_override_home │ └── main.html ├── lambda_forge ├── __init__.py ├── builders │ ├── __init__.py │ ├── services │ │ ├── kms.py │ │ ├── cognito.py │ │ ├── layers.py │ │ ├── secrets_manager.py │ │ ├── websockets.py │ │ ├── event_bridge.py │ │ ├── sns.py │ │ ├── sqs.py │ │ ├── s3.py │ │ └── dynamodb.py │ └── file_service.py ├── logs │ ├── __init__.py │ ├── tui │ │ ├── ui │ │ │ ├── __init__.py │ │ │ ├── screens │ │ │ │ ├── __init__.py │ │ │ │ └── index.py │ │ │ ├── widgets │ │ │ │ ├── __init__.py │ │ │ │ ├── header.py │ │ │ │ └── cloudwatch_single_log.py │ │ │ └── tui.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ └── _test_data.py │ │ └── __init__.py │ └── launch_tui.py ├── scaffold │ ├── README.md │ ├── infra │ │ ├── __init__.py │ │ ├── stacks │ │ │ ├── __init__.py │ │ │ └── lambda_stack.py │ │ ├── stages │ │ │ ├── __init__.py │ │ │ └── deploy.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── aws_lambda.py │ │ │ └── api_gateway.py │ ├── .coveragerc │ ├── pytest.ini │ └── requirements.txt ├── api_gateway │ └── __init__.py ├── live │ ├── tui │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── file_watcher.py │ │ │ └── forge.py │ │ ├── ui │ │ │ ├── screens │ │ │ │ ├── __init__.py │ │ │ │ └── index.py │ │ │ ├── widgets │ │ │ │ ├── triggers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── _trigger_submit.py │ │ │ │ │ ├── event_bridge.py │ │ │ │ │ ├── sqs.py │ │ │ │ │ ├── sns.py │ │ │ │ │ ├── triggers.py │ │ │ │ │ ├── s3.py │ │ │ │ │ └── api_gateway.py │ │ │ │ ├── __init__.py │ │ │ │ ├── header.py │ │ │ │ └── server.py │ │ │ └── tui.py │ │ └── __init__.py │ ├── awsiot.zip │ ├── __init__.py │ ├── live_iam.py │ ├── live_sns.py │ ├── live_event.py │ ├── main.py │ └── live_sqs.py ├── stacks │ ├── minimal │ │ ├── app.py │ │ └── stack.py │ ├── default │ │ ├── app.py │ │ └── dev_stack.py │ └── no_docs │ │ ├── app.py │ │ ├── dev_stack.py │ │ └── prod_stack.py ├── constants.py └── path.py ├── MANIFEST.in ├── ecr ├── generate_redoc.py ├── base-requirements.txt ├── additional_fixtures.py ├── generate_swagger.py ├── Dockerfile ├── validate_integration_tests.py └── generate_wiki.py ├── .github └── workflows │ └── actions.yaml ├── LICENSE ├── CODE_OF_CONDUCT.md ├── setup.py ├── CONTRIBUTING.md └── deploy.py /docs/demo/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/docs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/infra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/layers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/authorizers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/docs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/infra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/layers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/builders/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/logs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/functions/custom/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/infra/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/infra/stacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/infra/stages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.lambda-forge.com -------------------------------------------------------------------------------- /docs/examples/authorizers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/infra/stacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/infra/stages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/site/CNAME: -------------------------------------------------------------------------------- 1 | docs.lambda-forge.com -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/authorizers/secret/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/functions/external/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/functions/hello_world/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/functions/private/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/authorizers/jwt/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/authorizers/sso/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/hello/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/signin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/signup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/feed/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/connect/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/stacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/stages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/comment_post/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/create_post/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_post/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/like_post/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/update_post/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_message/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/images/mailer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/images/qrcode/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/redirect/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/shortener/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_comment/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_connection_id/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/make_guess/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/create_game/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/layers/sm_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .sm_utils import * 2 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | title: Home 4 | --- 5 | -------------------------------------------------------------------------------- /docs/demo/layers/my_custom_layer/__init__.py: -------------------------------------------------------------------------------- 1 | from .my_custom_layer import * 2 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.jp.min.js: -------------------------------------------------------------------------------- 1 | module.exports=require("./lunr.ja"); -------------------------------------------------------------------------------- /lambda_forge/api_gateway/__init__.py: -------------------------------------------------------------------------------- 1 | from .rest import REST 2 | from .wss import WSS 3 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .forge import ForgeAPI 2 | 3 | __all__ = ['ForgeAPI'] 4 | -------------------------------------------------------------------------------- /docs/demo/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/demo/diagram.png -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/screens/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import Index 2 | 3 | __all__ = ["Index"] 4 | 5 | -------------------------------------------------------------------------------- /docs/demo/.coveragerc: -------------------------------------------------------------------------------- 1 | 2 | [run] 3 | branch = True 4 | include = 5 | functions/**/main.py 6 | 7 | omit= 8 | -------------------------------------------------------------------------------- /docs/docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/images/logo.png -------------------------------------------------------------------------------- /docs/examples/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/examples/diagram.png -------------------------------------------------------------------------------- /docs/site/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/assets/logo.png -------------------------------------------------------------------------------- /docs/site/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/images/logo.png -------------------------------------------------------------------------------- /docs/site/sitemap.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/sitemap.xml.gz -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/screens/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import IndexScreen 2 | 3 | __all__ = ["IndexScreen"] 4 | -------------------------------------------------------------------------------- /lambda_forge/logs/tui/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .forge_logs import ForgeLogsAPI 2 | 3 | __all__ = ["ForgeLogsAPI"] 4 | -------------------------------------------------------------------------------- /docs/docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/docs/examples/blog.md: -------------------------------------------------------------------------------- 1 | # A Serverless Blog Application using AWS Cognito, Dynamo DB and S3 2 | 3 | Coming soon... 4 | -------------------------------------------------------------------------------- /docs/docs/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/images/favicon.png -------------------------------------------------------------------------------- /docs/site/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/assets/favicon.png -------------------------------------------------------------------------------- /docs/site/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/images/favicon.png -------------------------------------------------------------------------------- /lambda_forge/live/awsiot.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/lambda_forge/live/awsiot.zip -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/__init__.py: -------------------------------------------------------------------------------- 1 | from .triggers import Triggers 2 | 3 | __all__ = ["Triggers"] 4 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/.coveragerc: -------------------------------------------------------------------------------- 1 | 2 | [run] 3 | branch = True 4 | include = 5 | functions/**/main.py 6 | 7 | omit= 8 | -------------------------------------------------------------------------------- /docs/demo/layers/my_custom_layer/my_custom_layer.py: -------------------------------------------------------------------------------- 1 | def hello_from_layer(): 2 | return "Hello from my_custom_layer layer!" 3 | -------------------------------------------------------------------------------- /docs/docs/assets/lambda-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/assets/lambda-forge.png -------------------------------------------------------------------------------- /docs/docs/examples/images/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/auth.png -------------------------------------------------------------------------------- /docs/docs/examples/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/email.png -------------------------------------------------------------------------------- /docs/docs/home/images/sns-live.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/sns-live.gif -------------------------------------------------------------------------------- /docs/docs/images/lambda-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/images/lambda-forge.png -------------------------------------------------------------------------------- /docs/site/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/site/assets/lambda-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/assets/lambda-forge.png -------------------------------------------------------------------------------- /docs/site/examples/images/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/auth.png -------------------------------------------------------------------------------- /docs/site/examples/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/email.png -------------------------------------------------------------------------------- /docs/site/home/images/sns-live.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/sns-live.gif -------------------------------------------------------------------------------- /docs/site/images/lambda-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/images/lambda-forge.png -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | from .cloudwatch_log import CloudWatchLogs 2 | from .header import ForgeLogsHeader 3 | -------------------------------------------------------------------------------- /docs/docs/examples/images/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/qrcode.png -------------------------------------------------------------------------------- /docs/docs/examples/images/scraper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/scraper.png -------------------------------------------------------------------------------- /docs/docs/examples/images/sender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/sender.png -------------------------------------------------------------------------------- /docs/docs/home/images/default-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/default-dev.png -------------------------------------------------------------------------------- /docs/docs/home/images/live-server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/live-server.gif -------------------------------------------------------------------------------- /docs/docs/home/images/live-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/live-server.png -------------------------------------------------------------------------------- /docs/docs/home/images/sns-trigger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/sns-trigger.gif -------------------------------------------------------------------------------- /docs/docs/home/images/updated-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/updated-dev.png -------------------------------------------------------------------------------- /docs/site/examples/images/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/qrcode.png -------------------------------------------------------------------------------- /docs/site/examples/images/scraper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/scraper.png -------------------------------------------------------------------------------- /docs/site/examples/images/sender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/sender.png -------------------------------------------------------------------------------- /docs/site/home/images/default-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/default-dev.png -------------------------------------------------------------------------------- /docs/site/home/images/live-server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/live-server.gif -------------------------------------------------------------------------------- /docs/site/home/images/live-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/live-server.png -------------------------------------------------------------------------------- /docs/site/home/images/sns-trigger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/sns-trigger.gif -------------------------------------------------------------------------------- /docs/site/home/images/updated-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/updated-dev.png -------------------------------------------------------------------------------- /docs/site/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/docs/examples/images/receiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/receiver.png -------------------------------------------------------------------------------- /docs/docs/home/images/dev-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/dev-helloworld.png -------------------------------------------------------------------------------- /docs/docs/home/images/live-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/live-trigger.png -------------------------------------------------------------------------------- /docs/docs/home/images/prod-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/prod-success.png -------------------------------------------------------------------------------- /docs/site/examples/images/receiver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/receiver.png -------------------------------------------------------------------------------- /docs/site/home/images/dev-helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/dev-helloworld.png -------------------------------------------------------------------------------- /docs/site/home/images/live-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/live-trigger.png -------------------------------------------------------------------------------- /docs/site/home/images/prod-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/prod-success.png -------------------------------------------------------------------------------- /docs/docs/examples/images/chat-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/chat-diagram.png -------------------------------------------------------------------------------- /docs/docs/examples/images/pass-exposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/pass-exposed.png -------------------------------------------------------------------------------- /docs/docs/examples/images/pre-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/pre-connect.png -------------------------------------------------------------------------------- /docs/docs/examples/images/prod-scraper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/prod-scraper.png -------------------------------------------------------------------------------- /docs/docs/home/images/live-apigateway.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/live-apigateway.gif -------------------------------------------------------------------------------- /docs/docs/home/images/prod-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/prod-hello-world.png -------------------------------------------------------------------------------- /docs/docs/home/images/staging-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/staging-success.png -------------------------------------------------------------------------------- /docs/examples/.coveragerc: -------------------------------------------------------------------------------- 1 | 2 | [run] 3 | branch = True 4 | include = 5 | functions/**/main.py 6 | functions/**/utils.py 7 | 8 | omit= 9 | -------------------------------------------------------------------------------- /docs/site/examples/images/chat-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/chat-diagram.png -------------------------------------------------------------------------------- /docs/site/examples/images/pass-exposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/pass-exposed.png -------------------------------------------------------------------------------- /docs/site/examples/images/pre-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/pre-connect.png -------------------------------------------------------------------------------- /docs/site/examples/images/prod-scraper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/prod-scraper.png -------------------------------------------------------------------------------- /docs/site/home/images/live-apigateway.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/live-apigateway.gif -------------------------------------------------------------------------------- /docs/site/home/images/prod-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/prod-hello-world.png -------------------------------------------------------------------------------- /docs/site/home/images/staging-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/staging-success.png -------------------------------------------------------------------------------- /docs/docs/examples/images/qrcode-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/qrcode-diagram.png -------------------------------------------------------------------------------- /docs/docs/examples/images/sender-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/sender-connect.png -------------------------------------------------------------------------------- /docs/docs/examples/images/three_pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/three_pipelines.png -------------------------------------------------------------------------------- /docs/docs/examples/images/url-shortener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/url-shortener.png -------------------------------------------------------------------------------- /docs/docs/home/images/integration-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/integration-failed.png -------------------------------------------------------------------------------- /docs/docs/home/images/integrations-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/integrations-failed.png -------------------------------------------------------------------------------- /docs/docs/home/images/only-dev-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/only-dev-pipeline.png -------------------------------------------------------------------------------- /docs/docs/home/images/only-dev-triggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/only-dev-triggers.png -------------------------------------------------------------------------------- /docs/docs/home/images/staging-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/staging-hello-world.png -------------------------------------------------------------------------------- /docs/docs/home/images/success-pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/success-pipelines.png -------------------------------------------------------------------------------- /docs/docs/theme_override_home/.icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/theme_override_home/.icons/logo.png -------------------------------------------------------------------------------- /docs/site/examples/images/qrcode-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/qrcode-diagram.png -------------------------------------------------------------------------------- /docs/site/examples/images/sender-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/sender-connect.png -------------------------------------------------------------------------------- /docs/site/examples/images/three_pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/three_pipelines.png -------------------------------------------------------------------------------- /docs/site/examples/images/url-shortener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/url-shortener.png -------------------------------------------------------------------------------- /docs/site/home/images/integration-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/integration-failed.png -------------------------------------------------------------------------------- /docs/site/home/images/integrations-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/integrations-failed.png -------------------------------------------------------------------------------- /docs/site/home/images/only-dev-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/only-dev-pipeline.png -------------------------------------------------------------------------------- /docs/site/home/images/only-dev-triggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/only-dev-triggers.png -------------------------------------------------------------------------------- /docs/site/home/images/staging-hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/staging-hello-world.png -------------------------------------------------------------------------------- /docs/site/home/images/success-pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/success-pipelines.png -------------------------------------------------------------------------------- /docs/docs/examples/images/guess-the-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/guess-the-number.png -------------------------------------------------------------------------------- /docs/docs/examples/images/receiver-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/receiver-connect.png -------------------------------------------------------------------------------- /docs/docs/home/images/deployed-hello-worlds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/deployed-hello-worlds.png -------------------------------------------------------------------------------- /docs/docs/home/images/prod-pipeline-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/prod-pipeline-running.png -------------------------------------------------------------------------------- /docs/docs/home/images/staging-running-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/staging-running-only.png -------------------------------------------------------------------------------- /docs/docs/theme_override_home/.icons/puzzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/theme_override_home/.icons/puzzle.png -------------------------------------------------------------------------------- /docs/site/examples/images/guess-the-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/guess-the-number.png -------------------------------------------------------------------------------- /docs/site/examples/images/receiver-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/receiver-connect.png -------------------------------------------------------------------------------- /docs/site/home/images/deployed-hello-worlds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/deployed-hello-worlds.png -------------------------------------------------------------------------------- /docs/site/home/images/prod-pipeline-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/prod-pipeline-running.png -------------------------------------------------------------------------------- /docs/site/home/images/staging-running-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/staging-running-only.png -------------------------------------------------------------------------------- /lambda_forge/live/tui/__init__.py: -------------------------------------------------------------------------------- 1 | from lambda_forge.live.tui.ui.tui import ForgeTUI 2 | 3 | 4 | def launch_forge_tui(): 5 | 6 | ForgeTUI().run() 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include lambda_forge/live/* 2 | include lambda_forge/logs/* 3 | recursive-include lambda_forge/live * 4 | recursive-include lambda_forge/logs * 5 | -------------------------------------------------------------------------------- /docs/docs/theme_override_home/.icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/theme_override_home/.icons/favicon.png -------------------------------------------------------------------------------- /lambda_forge/logs/launch_tui.py: -------------------------------------------------------------------------------- 1 | from lambda_forge.logs.tui import launch_forge_logs_tui 2 | 3 | 4 | def launch(params): 5 | launch_forge_logs_tui(params) 6 | -------------------------------------------------------------------------------- /docs/docs/examples/images/lambda-forge-short-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/lambda-forge-short-url.png -------------------------------------------------------------------------------- /docs/site/examples/images/lambda-forge-short-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/lambda-forge-short-url.png -------------------------------------------------------------------------------- /lambda_forge/stacks/minimal/app.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from infra.stacks.stack import Stack 3 | 4 | app = cdk.App() 5 | 6 | Stack(app) 7 | 8 | app.synth() 9 | -------------------------------------------------------------------------------- /docs/docs/examples/images/three_example_pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/examples/images/three_example_pipelines.png -------------------------------------------------------------------------------- /docs/docs/theme_override_home/.icons/lambda-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/theme_override_home/.icons/lambda-forge.png -------------------------------------------------------------------------------- /docs/site/examples/images/three_example_pipelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/examples/images/three_example_pipelines.png -------------------------------------------------------------------------------- /docs/docs/home/images/staging-hello-world-api-gateway-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/docs/home/images/staging-hello-world-api-gateway-trigger.png -------------------------------------------------------------------------------- /docs/site/home/images/staging-hello-world-api-gateway-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuiPimenta-Dev/lambda-forge/HEAD/docs/site/home/images/staging-hello-world-api-gateway-trigger.png -------------------------------------------------------------------------------- /docs/demo/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = unit.py integration.py main.py 3 | norecursedirs = cdk.out 4 | markers = 5 | integration(method, endpoint): mark a test as an integration test. -------------------------------------------------------------------------------- /docs/examples/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = unit.py integration.py main.py 3 | norecursedirs = cdk.out 4 | markers = 5 | integration(method, endpoint): mark a test as an integration test. -------------------------------------------------------------------------------- /lambda_forge/logs/tui/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | from .ui.tui import ForgeLogsApp 3 | 4 | def launch_forge_logs_tui(params: Optional[Dict]): 5 | ForgeLogsApp(params).run() 6 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = unit.py integration.py main.py 3 | norecursedirs = cdk.out 4 | markers = 5 | integration(method, endpoint): mark a test as an integration test. -------------------------------------------------------------------------------- /ecr/generate_redoc.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | if __name__ == "__main__": 4 | subprocess.run(["python", "generate_api_docs.py"], check=True) 5 | subprocess.run(["redoc-cli", "bundle", "-o", "redoc.html", "docs.yaml"], check=True) 6 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/_trigger_submit.py: -------------------------------------------------------------------------------- 1 | from rich.console import RenderableType 2 | from textual.widgets import Button 3 | 4 | 5 | class TriggerSubmit(Button): 6 | def render(self) -> RenderableType: 7 | return "Submit" 8 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | from .logs import LogStream 2 | from .server import ServerTable 3 | from .triggers import Triggers 4 | from .header import ForgeHeader 5 | 6 | 7 | __all__ = ["LogStream", "ServerTable", "Triggers", "ForgeHeader"] 8 | -------------------------------------------------------------------------------- /docs/demo/functions/private/unit.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .main import lambda_handler 4 | 5 | 6 | def test_lambda_handler(): 7 | 8 | response = lambda_handler(None, None) 9 | 10 | assert response["body"] == json.dumps({"message": "Hello World!"}) 11 | -------------------------------------------------------------------------------- /docs/demo/functions/hello_world/unit.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .main import lambda_handler 4 | 5 | 6 | def test_lambda_handler(): 7 | 8 | response = lambda_handler(None, None) 9 | 10 | assert response["body"] == json.dumps({"message": "Hello World!"}) 11 | -------------------------------------------------------------------------------- /lambda_forge/live/__init__.py: -------------------------------------------------------------------------------- 1 | from .live import Live 2 | from .live_apigtw import LiveApiGtw 3 | from .live_event import LiveEventBridge 4 | from .live_lambda import LiveLambda 5 | from .live_s3 import LiveS3 6 | from .live_sns import LiveSNS 7 | from .live_sqs import LiveSQS 8 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_message/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def lambda_handler(event, context): 5 | 6 | return { 7 | "statusCode": 200, 8 | "body": json.dumps({"message": "Hello World!"}), 9 | "headers": {"Access-Control-Allow-Origin": "*"}, 10 | } 11 | -------------------------------------------------------------------------------- /docs/demo/app.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from infra.stacks.dev_stack import DevStack 3 | from infra.stacks.prod_stack import ProdStack 4 | from infra.stacks.staging_stack import StagingStack 5 | 6 | app = cdk.App() 7 | 8 | DevStack(app) 9 | StagingStack(app) 10 | ProdStack(app) 11 | 12 | app.synth() 13 | -------------------------------------------------------------------------------- /docs/demo/functions/custom/unit.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .main import lambda_handler 4 | 5 | 6 | def test_lambda_handler(): 7 | 8 | response = lambda_handler(None, None) 9 | 10 | assert response["body"] == json.dumps( 11 | {"message": "Hello from my_custom_layer layer!"} 12 | ) 13 | -------------------------------------------------------------------------------- /docs/examples/app.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from infra.stacks.dev_stack import DevStack 3 | from infra.stacks.prod_stack import ProdStack 4 | from infra.stacks.staging_stack import StagingStack 5 | 6 | app = cdk.App() 7 | 8 | DevStack(app) 9 | StagingStack(app) 10 | ProdStack(app) 11 | 12 | app.synth() 13 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==22.1.0 2 | aws-cdk-lib==2.29.1 3 | constructs>=10.0.0,<11.0.0 4 | black==22.10.0 5 | boto3==1.26.25 6 | click==8.1.3 7 | pre-commit==2.20.0 8 | PyYAML==6.0 9 | requests==2.28.1 10 | pytest==7.2.0 11 | pytest-dotenv==0.5.2 12 | moto==4.1.7 13 | requests==2.28.1 14 | coverage==7.2.3 -------------------------------------------------------------------------------- /lambda_forge/stacks/default/app.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from infra.stacks.dev_stack import DevStack 3 | from infra.stacks.prod_stack import ProdStack 4 | from infra.stacks.staging_stack import StagingStack 5 | 6 | app = cdk.App() 7 | 8 | DevStack(app) 9 | StagingStack(app) 10 | ProdStack(app) 11 | 12 | app.synth() 13 | -------------------------------------------------------------------------------- /lambda_forge/stacks/no_docs/app.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from infra.stacks.dev_stack import DevStack 3 | from infra.stacks.prod_stack import ProdStack 4 | from infra.stacks.staging_stack import StagingStack 5 | 6 | app = cdk.App() 7 | 8 | DevStack(app) 9 | StagingStack(app) 10 | ProdStack(app) 11 | 12 | app.synth() 13 | -------------------------------------------------------------------------------- /docs/demo/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==22.1.0 2 | aws-cdk-lib==2.29.1 3 | constructs>=10.0.0,<11.0.0 4 | black==22.10.0 5 | boto3==1.26.25 6 | click==8.1.3 7 | pre-commit==2.20.0 8 | PyYAML==6.0 9 | requests==2.28.1 10 | pytest==7.2.0 11 | pytest-dotenv==0.5.2 12 | moto==4.1.7 13 | requests==2.28.1 14 | coverage==7.2.3 15 | requests==2.31.0 -------------------------------------------------------------------------------- /ecr/base-requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==22.1.0 2 | aws-cdk-lib==2.29.1 3 | constructs>=10.0.0,<11.0.0 4 | black==22.10.0 5 | boto3==1.26.25 6 | click==8.1.3 7 | pre-commit==2.20.0 8 | PyYAML==6.0 9 | requests==2.28.1 10 | pytest==7.2.0 11 | pytest-dotenv==0.5.2 12 | moto==4.1.7 13 | requests==2.28.1 14 | coverage==7.2.3 15 | diagrams==0.23.4 -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/services/__init__.py: -------------------------------------------------------------------------------- 1 | from infra.services.api_gateway import APIGateway 2 | from infra.services.aws_lambda import Lambda 3 | 4 | 5 | class Services: 6 | def __init__(self, scope, context) -> None: 7 | self.api_gateway = APIGateway(scope, context) 8 | self.aws_lambda = Lambda(scope, context) 9 | -------------------------------------------------------------------------------- /lambda_forge/constants.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import NamedTuple 3 | 4 | 5 | def get_base_url(): 6 | with open("cdk.json") as f: 7 | return json.load(f)["context"]["base_url"] 8 | 9 | 10 | BASE_URL = get_base_url() 11 | 12 | 13 | class ECR(NamedTuple): 14 | LATEST = "public.ecr.aws/w1u4u5r2/lambda-forge:latest" 15 | -------------------------------------------------------------------------------- /docs/demo/functions/external/unit.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .main import lambda_handler 4 | 5 | 6 | def test_lambda_handler(): 7 | 8 | response = lambda_handler(None, None) 9 | body = json.loads(response["body"]) 10 | 11 | assert ["gender", "name", "location", "email", "login", "phone"] == list( 12 | body.keys() 13 | ) 14 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/kms.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_kms as kms 2 | 3 | 4 | class KMS: 5 | def __init__(self, scope, context) -> None: 6 | 7 | # self.kms = kms.Key.from_key_arn( 8 | # scope, 9 | # "KMS", 10 | # key_arn=context.resources["arns"]["kms_arn"], 11 | # ) 12 | ... 13 | -------------------------------------------------------------------------------- /docs/demo/infra/stages/deploy.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from constructs import Construct 3 | from infra.stacks.lambda_stack import LambdaStack 4 | 5 | 6 | class DeployStage(cdk.Stage): 7 | def __init__(self, scope: Construct, context, **kwargs): 8 | super().__init__(scope, context.stage, **kwargs) 9 | 10 | LambdaStack(self, context) 11 | -------------------------------------------------------------------------------- /docs/examples/infra/stages/deploy.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from constructs import Construct 3 | from infra.stacks.lambda_stack import LambdaStack 4 | 5 | 6 | class DeployStage(cdk.Stage): 7 | def __init__(self, scope: Construct, context, **kwargs): 8 | super().__init__(scope, context.stage, **kwargs) 9 | 10 | LambdaStack(self, context) 11 | -------------------------------------------------------------------------------- /docs/demo/functions/custom/integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import requests 3 | 4 | from lambda_forge.constants import BASE_URL 5 | 6 | 7 | @pytest.mark.integration(method="GET", endpoint="/custom") 8 | def test_using_custom_layer_status_code_is_200(): 9 | 10 | response = requests.get(url=f"{BASE_URL}/custom") 11 | 12 | assert response.status_code == 200 13 | -------------------------------------------------------------------------------- /docs/demo/functions/external/integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import requests 3 | 4 | from lambda_forge.constants import BASE_URL 5 | 6 | 7 | @pytest.mark.integration(method="GET", endpoint="/external") 8 | def test_external_status_code_is_200(): 9 | 10 | response = requests.get(url=f"{BASE_URL}/external") 11 | 12 | assert response.status_code == 200 13 | -------------------------------------------------------------------------------- /ecr/additional_fixtures.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def pytest_html_report_title(report): 5 | report.title = "Test Report" 6 | 7 | 8 | def pytest_generate_tests(metafunc): 9 | for mark in metafunc.definition.iter_markers(name="integration"): 10 | with open("tested_endpoints.txt", "a") as f: 11 | f.write(f"{json.dumps(mark.kwargs)}|") 12 | -------------------------------------------------------------------------------- /docs/examples/infra/services/kms.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_kms as kms 2 | 3 | 4 | class KMS: 5 | def __init__(self, scope, context) -> None: 6 | 7 | self.auth_key = kms.Key.from_key_arn( 8 | scope, 9 | "SignUpKey", 10 | key_arn="arn:aws:kms:us-east-2:211125768252:key/bb085039-a653-4b38-abad-b6dd4ce11ea4", 11 | ) 12 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/stages/deploy.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from constructs import Construct 3 | from infra.stacks.lambda_stack import LambdaStack 4 | 5 | 6 | class DeployStage(cdk.Stage): 7 | def __init__(self, scope: Construct, context, **kwargs): 8 | super().__init__(scope, context.stage, **kwargs) 9 | 10 | LambdaStack(self, context) 11 | -------------------------------------------------------------------------------- /docs/demo/functions/hello_world/integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import requests 3 | 4 | from lambda_forge.constants import BASE_URL 5 | 6 | 7 | @pytest.mark.integration(method="GET", endpoint="/hello_world") 8 | def test_hello_world_status_code_is_200(): 9 | 10 | response = requests.get(url=f"{BASE_URL}/hello_world") 11 | 12 | assert response.status_code == 200 13 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/cognito.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_cognito as cognito 2 | 3 | 4 | class Cognito: 5 | def __init__(self, scope, context) -> None: 6 | 7 | # self.cognito = cognito.UserPool.from_user_pool_arn( 8 | # scope, 9 | # "Cognito", 10 | # user_pool_arn=context.resources["arns"]["cognito_arn"], 11 | # ) 12 | ... 13 | -------------------------------------------------------------------------------- /docs/examples/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==22.1.0 2 | aws-cdk-lib==2.29.1 3 | constructs>=10.0.0,<11.0.0 4 | black==22.10.0 5 | boto3==1.26.25 6 | click==8.1.3 7 | pre-commit==2.20.0 8 | PyYAML==6.0 9 | pytest-dotenv==0.5.2 10 | moto==4.1.7 11 | requests==2.28.1 12 | coverage==7.2.3 13 | requests-mock==1.11.0 14 | pillow==10.3.0 15 | requests==2.28.1 16 | beautifulsoup4==4.12.3 17 | qrcode==7.4.2 18 | jwt==1.3.1 -------------------------------------------------------------------------------- /lambda_forge/builders/services/layers.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda as _lambda 2 | 3 | from lambda_forge import Path 4 | 5 | 6 | class Layers: 7 | def __init__(self, scope) -> None: 8 | 9 | # self.layer = _lambda.LayerVersion.from_layer_version_arn( 10 | # scope, 11 | # id="Layer", 12 | # layer_version_arn="", 13 | # ) 14 | ... 15 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/stacks/lambda_stack.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import Stack 2 | from constructs import Construct 3 | from infra.services import Services 4 | 5 | 6 | class LambdaStack(Stack): 7 | def __init__(self, scope: Construct, context, **kwargs) -> None: 8 | 9 | super().__init__(scope, f"{context.name}-Lambda-Stack", **kwargs) 10 | 11 | self.services = Services(self, context) 12 | -------------------------------------------------------------------------------- /docs/examples/infra/services/cognito.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_cognito as cognito 2 | 3 | 4 | class Cognito: 5 | def __init__(self, scope, context) -> None: 6 | 7 | self.blog_user_pool = cognito.UserPool.from_user_pool_arn( 8 | scope, 9 | f"{context.stage}-blog-user-pool", 10 | "arn:aws:cognito-idp:us-east-2:211125768252:userpool/us-east-2_JKQ7WdFVv", 11 | ) 12 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/tui.py: -------------------------------------------------------------------------------- 1 | from textual.app import App 2 | from textual.binding import Binding 3 | from .screens import IndexScreen 4 | 5 | 6 | class ForgeTUI(App): 7 | SCREENS = { 8 | "index": IndexScreen(), 9 | } 10 | CSS_PATH = "styles.css" 11 | BINDINGS = [ 12 | Binding("q", "quit", "Quit"), 13 | ] 14 | 15 | def on_mount(self) -> None: 16 | self.push_screen("index") 17 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/hello/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Input: 7 | pass 8 | 9 | 10 | @dataclass 11 | class Output: 12 | message: str 13 | 14 | 15 | def lambda_handler(event, context): 16 | 17 | email = event["requestContext"]["authorizer"]["email"] 18 | 19 | return {"statusCode": 200, "body": json.dumps({"message": f"Hello, {email}!"})} 20 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/secrets_manager.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_secretsmanager as sm 2 | 3 | 4 | class SecretsManager: 5 | def __init__(self, scope, context) -> None: 6 | 7 | # self.sm = sm.Secret.from_secret_complete_arn( 8 | # scope, 9 | # id="SecretsManager", 10 | # secret_complete_arn=context.resources["arns"]["secrets_manager_arn"], 11 | # ) 12 | ... 13 | -------------------------------------------------------------------------------- /docs/demo/functions/private/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Input: 7 | pass 8 | 9 | 10 | @dataclass 11 | class Output: 12 | message: str 13 | 14 | 15 | def lambda_handler(event, context): 16 | 17 | return { 18 | "statusCode": 200, 19 | "body": json.dumps({"message": "Hello World!"}), 20 | "headers": {"Access-Control-Allow-Origin": "*"}, 21 | } 22 | -------------------------------------------------------------------------------- /docs/demo/functions/hello_world/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Input: 7 | pass 8 | 9 | 10 | @dataclass 11 | class Output: 12 | message: str 13 | 14 | 15 | def lambda_handler(event, context): 16 | 17 | return { 18 | "statusCode": 200, 19 | "body": json.dumps({"message": "hello my friend!"}), 20 | "headers": {"Access-Control-Allow-Origin": "*"}, 21 | } 22 | -------------------------------------------------------------------------------- /docs/demo/functions/private/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class PrivateConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Private", 9 | path="./functions/private", 10 | description="A private function", 11 | ) 12 | 13 | services.api_gateway.create_endpoint("GET", "/private", function) 14 | -------------------------------------------------------------------------------- /ecr/generate_swagger.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | if __name__ == "__main__": 4 | subprocess.run(["python", "generate_api_docs.py"], check=True) 5 | with open("docs.yaml", "r") as input_file: 6 | with open("swagger.html", "w") as output_file: 7 | subprocess.run( 8 | ["python", "swagger_yml_to_ui.py"], 9 | stdin=input_file, 10 | stdout=output_file, 11 | check=True, 12 | ) 13 | -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/tui.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | from textual.app import App 3 | from .screens import Index 4 | from ..api import ForgeLogsAPI 5 | 6 | 7 | class ForgeLogsApp(App): 8 | CSS_PATH = "styles.css" 9 | SCREENS = {"index": Index()} 10 | 11 | def __init__(self, params: Optional[Dict]): 12 | super().__init__() 13 | self.logs_api = ForgeLogsAPI(params) 14 | 15 | def on_mount(self) -> None: 16 | self.push_screen("index") 17 | -------------------------------------------------------------------------------- /docs/demo/infra/services/__init__.py: -------------------------------------------------------------------------------- 1 | from infra.services.api_gateway import APIGateway 2 | from infra.services.aws_lambda import Lambda 3 | from infra.services.layers import Layers 4 | from infra.services.sns import SNS 5 | 6 | 7 | class Services: 8 | def __init__(self, scope, context) -> None: 9 | self.api_gateway = APIGateway(scope, context) 10 | self.aws_lambda = Lambda(scope, context) 11 | self.layers = Layers(scope) 12 | self.sns = SNS(scope, context) 13 | -------------------------------------------------------------------------------- /docs/demo/functions/custom/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | import my_custom_layer 5 | 6 | 7 | @dataclass 8 | class Input: 9 | pass 10 | 11 | 12 | @dataclass 13 | class Output: 14 | message: str 15 | 16 | 17 | def lambda_handler(event, context): 18 | 19 | message = my_custom_layer.hello_from_layer() 20 | 21 | return { 22 | "statusCode": 200, 23 | "body": json.dumps({"message": message}), 24 | "headers": {"Access-Control-Allow-Origin": "*"}, 25 | } 26 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/hello/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class HelloConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Hello", 9 | path="./functions/auth", 10 | description="A private function", 11 | directory="hello", 12 | ) 13 | 14 | services.api_gateway.create_endpoint( 15 | "GET", "/hello", function, authorizer="jwt" 16 | ) 17 | -------------------------------------------------------------------------------- /docs/demo/authorizers/secret/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class SecretAuthorizerConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="SecretAuthorizer", 9 | path="./authorizers/secret", 10 | description="An authorizer to validate requests based on a secret present on the headers", 11 | ) 12 | 13 | services.api_gateway.create_authorizer(function, name="secret", default=True) 14 | -------------------------------------------------------------------------------- /docs/demo/functions/custom/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class CustomConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Custom", 9 | path="./functions/custom", 10 | description="A function to make use of the custom layer", 11 | layers=[services.layers.my_custom_layer], 12 | ) 13 | 14 | services.api_gateway.create_endpoint("GET", "/custom", function, public=True) 15 | -------------------------------------------------------------------------------- /docs/demo/functions/external/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class ExternalConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="External", 9 | path="./functions/external", 10 | description="A function that uses an external library", 11 | layers=[services.layers.requests_layer], 12 | ) 13 | 14 | services.api_gateway.create_endpoint("GET", "/external", function, public=True) 15 | -------------------------------------------------------------------------------- /docs/examples/authorizers/sso/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class SSOAuthorizerConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="SSOAuthorizer", 9 | path="./authorizers/sso", 10 | description="A cognito authorizer for private lambda functions", 11 | layers=[services.layers.pyjwt_layer], 12 | ) 13 | 14 | services.api_gateway.create_authorizer(function, name="sso", default=False) 15 | -------------------------------------------------------------------------------- /docs/demo/functions/hello_world/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class HelloWorldConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="HelloWorld", 9 | path="./functions/hello_world", 10 | description="A simple hello world", 11 | ) 12 | 13 | services.api_gateway.create_endpoint( 14 | "GET", "/hello_world", function, public=True 15 | ) 16 | 17 | services.sns.create_trigger("hello_world_topic", function) 18 | -------------------------------------------------------------------------------- /docs/examples/layers/sm_utils/sm_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import boto3 4 | 5 | 6 | def get_secret(secret_name: str): 7 | 8 | # Initialize the Secrets Manager client 9 | sm_client = boto3.client("secretsmanager") 10 | 11 | # Retrieve the secret value from Secrets Manager 12 | response = sm_client.get_secret_value(SecretId=secret_name) 13 | 14 | # Handle scenarios where the secret is stored as plain text instead of JSON. 15 | try: 16 | secret = json.loads(response["SecretString"]) 17 | 18 | except json.JSONDecodeError: 19 | secret = response["SecretString"] 20 | 21 | return secret 22 | -------------------------------------------------------------------------------- /docs/demo/docs/wikis/wiki.md: -------------------------------------------------------------------------------- 1 | # Wiki 2 | 3 | ## Introduction 4 | 5 | Welcome to our Documentation Hub! This platform serves as the central repository for all our project documentation, guidelines, and shared knowledge. It is designed to help our team collaborate more effectively and ensure everyone has access to the latest information. 6 | 7 | ## Purpose 8 | 9 | The purpose of this hub is to: 10 | 11 | - **Organize** our documentation in a central, easily accessible location. 12 | - **Share** knowledge and insights that can help each team member in their tasks. 13 | - **Improve** the continuity of information and make onboarding new team members smoother. 14 | -------------------------------------------------------------------------------- /docs/demo/infra/scripts/validate_todo.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | 5 | def check_functions_for_todo(json_file): 6 | with open(json_file, "r") as f: 7 | data = json.load(f) 8 | 9 | functions = data.get("functions", []) 10 | 11 | for function in functions: 12 | path = function.get("path", "") 13 | with open(path, "r") as file: 14 | if "# TODO" in file.read(): 15 | raise ValueError(f"TODO found in file: {path}") 16 | 17 | print("No TODO found in any files.") 18 | 19 | 20 | if __name__ == "__main__": 21 | json_file_path = "cdk.json" 22 | check_functions_for_todo(json_file_path) 23 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/feed/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class FeedConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Feed", 9 | path="./functions/blog", 10 | description="Get feed of posts", 11 | directory="feed", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint("GET", "/feed", function, authorizer="sso") 16 | 17 | services.dynamodb.posts_table.grant_read_data(function) 18 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/websockets.py: -------------------------------------------------------------------------------- 1 | from b_aws_websocket_api.ws_api import WsApi 2 | 3 | from lambda_forge.api_gateway import WSS 4 | 5 | 6 | class Websockets: 7 | def __init__(self, scope, context) -> None: 8 | 9 | wss = WsApi( 10 | scope=scope, 11 | id=context.create_id("Websocket"), 12 | name=context.create_id("Websocket"), 13 | route_selection_expression="$request.body.action", 14 | ) 15 | 16 | self.wss = WSS(scope=scope, context=context, wss=wss) 17 | 18 | def create_route(self, route_key, function): 19 | self.wss.create_route(route_key=route_key, function=function) 20 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/create_post/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class CreatePostConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="CreatePost", 9 | path="./functions/blog", 10 | description="Create a new post", 11 | directory="create_post", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "POST", "/posts", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | -------------------------------------------------------------------------------- /docs/demo/functions/private/integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import requests 3 | 4 | from lambda_forge.constants import BASE_URL 5 | 6 | 7 | @pytest.mark.integration(method="GET", endpoint="/private") 8 | def test_private_status_code_with_no_header_is_403(): 9 | 10 | response = requests.get(url=f"{BASE_URL}/private") 11 | 12 | assert response.status_code == 403 13 | 14 | 15 | @pytest.mark.integration(method="GET", endpoint="/private") 16 | def test_private_status_code_with_valid_header_is_200(): 17 | 18 | headers = {"secret": "CRMdDRMA4iW4xo9l38pACls7zsHYfp8T7TLXtucysb2lB5XBVFn8"} 19 | 20 | response = requests.get(url=f"{BASE_URL}/private", headers=headers) 21 | 22 | assert response.status_code == 200 23 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/redirect/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class RedirectConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Redirect", 9 | path="./functions/urls", 10 | description="Redirects from the short url to the original url", 11 | directory="redirect", 12 | environment={ 13 | "URLS_TABLE_NAME": services.dynamodb.urls_table.table_name, 14 | }, 15 | ) 16 | 17 | services.api_gateway.create_endpoint("GET", "/{url_id}", function, public=True) 18 | 19 | services.dynamodb.urls_table.grant_read_data(function) 20 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_post/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass 3 | 4 | import boto3 5 | 6 | 7 | @dataclass 8 | class Path: 9 | post_id: str 10 | 11 | 12 | @dataclass 13 | class Input: 14 | pass 15 | 16 | 17 | @dataclass 18 | class Output: 19 | pass 20 | 21 | 22 | def lambda_handler(event, context): 23 | 24 | dynamodb = boto3.resource("dynamodb") 25 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 26 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 27 | 28 | post_id = event.get("pathParameters", {}).get("post_id") 29 | 30 | posts_table.delete_item(Key={"PK": post_id}) 31 | 32 | return {"statusCode": 204, "headers": {"Access-Control-Allow-Origin": "*"}} 33 | -------------------------------------------------------------------------------- /docs/demo/infra/services/layers.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda as _lambda 2 | 3 | from lambda_forge.path import Path 4 | 5 | 6 | class Layers: 7 | def __init__(self, scope) -> None: 8 | 9 | self.my_custom_layer = _lambda.LayerVersion( 10 | scope, 11 | id="MyCustomLayerLayer", 12 | code=_lambda.Code.from_asset(Path.layer("layers/my_custom_layer")), 13 | compatible_runtimes=[_lambda.Runtime.PYTHON_3_9], 14 | description="", 15 | ) 16 | 17 | self.requests_layer = _lambda.LayerVersion.from_layer_version_arn( 18 | scope, 19 | id="RequestsLayer", 20 | layer_version_arn="arn:aws:lambda:us-east-2:211125768252:layer:requests:2", 21 | ) 22 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_connection_id/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import boto3 5 | 6 | 7 | def lambda_handler(event, context): 8 | 9 | # Retrieve the connection ID from the event 10 | connection_id = event["connection_id"] 11 | 12 | # Create a client for the API Gateway Management API 13 | api_gateway_management_client = boto3.client( 14 | "apigatewaymanagementapi", endpoint_url=os.environ.get("POST_TO_CONNECTION_URL") 15 | ) 16 | 17 | # Send the payload to the WebSocket 18 | api_gateway_management_client.post_to_connection( 19 | ConnectionId=connection_id, 20 | Data=json.dumps({"connection_id": connection_id}).encode("utf-8"), 21 | ) 22 | 23 | return {"statusCode": 200} 24 | -------------------------------------------------------------------------------- /docs/docs/theme_override_home/main.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | 5 | {% block libs %} 6 | 18 | {% endblock %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/site/theme_override_home/main.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | 5 | {% block libs %} 6 | 18 | {% endblock %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/create_game/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class CreateGameConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="CreateGame", 9 | path="./functions/guess_the_number", 10 | description="Creates a new guess the number game", 11 | directory="create_game", 12 | environment={ 13 | "NUMBERS_TABLE_NAME": services.dynamodb.numbers_table.table_name 14 | }, 15 | ) 16 | 17 | services.api_gateway.create_endpoint("POST", "/games", function, public=True) 18 | 19 | services.dynamodb.grant_write("numbers_table", function) 20 | -------------------------------------------------------------------------------- /lambda_forge/live/live_iam.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | import boto3 5 | 6 | 7 | class LiveIAM: 8 | def __init__(self, region): 9 | self.iam_client = boto3.client("iam", region_name=region) 10 | self.lambda_client = boto3.client("lambda", region_name=region) 11 | 12 | def attach_policy_to_lambda(self, policy_dict, function_arn, policy_name): 13 | response = self.lambda_client.get_function(FunctionName=function_arn) 14 | role_arn = response["Configuration"]["Role"] 15 | role_name = role_arn.split("/")[-1] 16 | self.iam_client.put_role_policy( 17 | RoleName=role_name, 18 | PolicyName=policy_name, 19 | PolicyDocument=json.dumps(policy_dict), 20 | ) 21 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/like_post/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class LikePostConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="LikePost", 9 | path="./functions/blog", 10 | description="Like a post", 11 | directory="like_post", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "POST", "/posts/{post_id}/like", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | services.dynamodb.posts_table.grant_read_data(function) 21 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.vi.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); -------------------------------------------------------------------------------- /docs/examples/functions/blog/update_post/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class UpdatePostConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="UpdatePost", 9 | path="./functions/blog", 10 | description="Update a post", 11 | directory="update_post", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "PUT", "/posts/{post_id}", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | services.dynamodb.posts_table.grant_read_data(function) 21 | -------------------------------------------------------------------------------- /docs/examples/infra/services/websockets.py: -------------------------------------------------------------------------------- 1 | from b_aws_websocket_api.ws_api import WsApi 2 | 3 | from lambda_forge.api_gateway import WSS 4 | from lambda_forge.trackers import trigger 5 | 6 | 7 | class Websockets: 8 | def __init__(self, scope, context) -> None: 9 | 10 | wss = WsApi( 11 | scope=scope, 12 | id=context.create_id("Websocket"), 13 | name=context.create_id("Websocket"), 14 | route_selection_expression="$request.body.action", 15 | ) 16 | 17 | self.wss = WSS(scope=scope, context=context, wss=wss) 18 | 19 | @trigger(service="wss", trigger="route_key", function="function") 20 | def create_route(self, route_key, function): 21 | self.wss.create_route(route_key=route_key, function=function) 22 | -------------------------------------------------------------------------------- /docs/examples/authorizers/jwt/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class JwtAuthorizerConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="JwtAuthorizer", 9 | path="./authorizers/jwt", 10 | description="A jwt authorizer for private lambda functions", 11 | layers=[services.layers.sm_utils_layer, services.layers.pyjwt_layer], 12 | environment={ 13 | "JWT_SECRET_NAME": services.secrets_manager.jwt_secret.secret_name 14 | }, 15 | ) 16 | 17 | services.api_gateway.create_authorizer(function, name="jwt", default=False) 18 | 19 | services.secrets_manager.jwt_secret.grant_read(function) 20 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_post/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class DeletePostConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="DeletePost", 9 | path="./functions/blog", 10 | description="Delete a post", 11 | directory="delete_post", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "DELETE", "/posts/{post_id}", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | services.dynamodb.posts_table.grant_read_data(function) 21 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/make_guess/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class MakeGuessConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="MakeGuess", 9 | path="./functions/guess_the_number", 10 | description="Make a guess for a particular game", 11 | directory="make_guess", 12 | environment={ 13 | "NUMBERS_TABLE_NAME": services.dynamodb.numbers_table.table_name 14 | }, 15 | ) 16 | 17 | services.api_gateway.create_endpoint( 18 | "GET", "/games/{game_id}", function, public=True 19 | ) 20 | 21 | services.dynamodb.numbers_table.grant_read_data(function) 22 | -------------------------------------------------------------------------------- /docs/examples/functions/images/qrcode/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class QrcodeConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Qrcode", 9 | path="./functions/images", 10 | description="Converts an image into a qr code", 11 | directory="qrcode", 12 | layers=[services.layers.qrcode_layer], 13 | environment={ 14 | "BUCKET_NAME": services.s3.images_bucket.bucket_name, 15 | }, 16 | ) 17 | 18 | services.api_gateway.create_endpoint( 19 | "POST", "/images/qrcode", function, public=True 20 | ) 21 | 22 | services.s3.grant_write("images_bucket", function) 23 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/comment_post/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class CommentPostConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="CommentPost", 9 | path="./functions/blog", 10 | description="Comment on a blog post", 11 | directory="comment_post", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "POST", "/posts/{post_id}/comments", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | services.dynamodb.posts_table.grant_read_data(function) 21 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_comment/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class DeleteCommentConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="DeleteComment", 9 | path="./functions/blog", 10 | description="Delete a comment", 11 | directory="delete_comment", 12 | environment={"POSTS_TABLE_NAME": services.dynamodb.posts_table.table_name}, 13 | ) 14 | 15 | services.api_gateway.create_endpoint( 16 | "DELETE", "/posts/{post_id}/comments", function, authorizer="sso" 17 | ) 18 | 19 | services.dynamodb.grant_write("posts_table", function) 20 | services.dynamodb.posts_table.grant_read_data(function) 21 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/connect/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class ConnectConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | send_connection_id_function = services.aws_lambda.functions["SendConnectionId"] 8 | 9 | connect_function = services.aws_lambda.create_function( 10 | name="Connect", 11 | path="./functions/chat", 12 | description="Handle the websocket connection", 13 | directory="connect", 14 | environment={ 15 | "TARGET_FUNCTION_ARN": send_connection_id_function.function_arn, 16 | }, 17 | ) 18 | 19 | services.websockets.create_route("$connect", connect_function) 20 | 21 | send_connection_id_function.grant_invoke(connect_function) 22 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/header.py: -------------------------------------------------------------------------------- 1 | from rich.text import Text 2 | from textual.widget import Widget 3 | 4 | 5 | class ForgeHeader(Widget): 6 | DEFAULT_CSS = """ 7 | ForgeHeader { 8 | padding: 1 4; 9 | } 10 | """ 11 | 12 | COMPONENT_CLASSES = {"title", "subtitle"} 13 | 14 | def __init__( 15 | self, 16 | title: str = "λ Lambda Forge", 17 | subtitle: str = "Simplify AWS Lambda deployments", 18 | ): 19 | super().__init__() 20 | self.title = title 21 | self.subtitle = subtitle 22 | 23 | def render(self): 24 | title = Text(self.title, style=self.get_component_rich_style("title")) 25 | subtitle = Text(self.subtitle, style=self.get_component_rich_style("subtitle")) 26 | 27 | return title + "\n" + subtitle 28 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/event_bridge.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Input 3 | 4 | from lambda_forge.live.tui.ui.widgets.text_area_theme import get_text_area 5 | 6 | from ._base import TriggerBaseWidget, TriggerBaseContainer 7 | 8 | 9 | class EventBridgeContainer(TriggerBaseContainer): 10 | DEFAULT_CSS = """ 11 | EventBridgeContainer { 12 | layout: grid; 13 | grid-size: 1 2; 14 | grid-rows: 5 10; 15 | } 16 | """ 17 | 18 | def compose(self) -> ComposeResult: 19 | yield Input(id="bus_name") 20 | yield get_text_area("message") 21 | 22 | 23 | class EventBridge(TriggerBaseWidget): 24 | service = "Event Bridge" 25 | def render_left(self) -> ComposeResult: 26 | yield EventBridgeContainer() 27 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/sqs.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Input, TextArea 3 | 4 | from lambda_forge.live.tui.ui.widgets.text_area_theme import get_text_area 5 | 6 | from ._base import TriggerBaseWidget, TriggerBaseContainer 7 | 8 | 9 | class SQSContainer(TriggerBaseContainer): 10 | DEFAULT_CSS = """ 11 | SQSContainer { 12 | layout: grid; 13 | grid-size: 1 2; 14 | grid-rows: 5 10; 15 | grid-columns: 1fr 1fr; 16 | } 17 | """ 18 | 19 | def compose(self) -> ComposeResult: 20 | yield Input(id="queue_url") 21 | yield get_text_area("message") 22 | 23 | 24 | class SQS(TriggerBaseWidget): 25 | service = "SQS" 26 | 27 | def render_left(self) -> ComposeResult: 28 | yield SQSContainer() 29 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/shortener/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class ShortenerConfig: 5 | def __init__(self, services: Services, context) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Shortener", 9 | path="./functions/urls", 10 | description="Creates a new short URL entry in DynamoDB mapping to the original url", 11 | directory="shortener", 12 | environment={ 13 | "URLS_TABLE_NAME": services.dynamodb.urls_table.table_name, 14 | "BASE_URL": context.resources["base_url"], 15 | }, 16 | ) 17 | 18 | services.api_gateway.create_endpoint("POST", "/urls", function, public=True) 19 | 20 | services.dynamodb.grant_write("urls_table", function) 21 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/api/file_watcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | 5 | class FileWatcher: 6 | def __init__(self, file_to_watch: Path): 7 | self.file_to_watch = file_to_watch 8 | self.last_modified_time = 0 9 | 10 | def get_modified_time(self): 11 | try: 12 | return os.path.getmtime(self.file_to_watch) 13 | except FileNotFoundError: 14 | return None 15 | 16 | def has_modified(self): 17 | current_modified_time = self.get_modified_time() 18 | if current_modified_time != self.last_modified_time: 19 | self.last_modified_time = current_modified_time 20 | return True 21 | return False 22 | 23 | def clear_file(self): 24 | with open(self.file_to_watch, "w") as f: 25 | f.write("") 26 | -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/widgets/header.py: -------------------------------------------------------------------------------- 1 | from rich.text import Text 2 | from textual.widget import Widget 3 | 4 | 5 | class ForgeLogsHeader(Widget): 6 | DEFAULT_CSS = """ 7 | ForgeLogsHeader { 8 | padding: 1 4; 9 | } 10 | """ 11 | 12 | COMPONENT_CLASSES = {"title", "subtitle"} 13 | 14 | def __init__( 15 | self, 16 | title: str = "λ Lambda Forge Logs", 17 | subtitle: str = "Simplify AWS Lambda Cloudwatch Logs", 18 | ): 19 | super().__init__() 20 | self.title = title 21 | self.subtitle = subtitle 22 | 23 | def render(self): 24 | title = Text(self.title, style=self.get_component_rich_style("title")) 25 | subtitle = Text(self.subtitle, style=self.get_component_rich_style("subtitle")) 26 | 27 | return title + "\n" + subtitle 28 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.multi.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="SignUp", 9 | path="./functions/auth", 10 | description="Securely handle user registration with unique credentials.", 11 | directory="signup", 12 | environment={ 13 | "AUTH_TABLE_NAME": services.dynamodb.auth_table.table_name, 14 | "KMS_KEY_ID": services.kms.auth_key.key_id, 15 | }, 16 | ) 17 | 18 | services.api_gateway.create_endpoint("POST", "/signup", function, public=True) 19 | 20 | services.dynamodb.grant_write("auth_table", function) 21 | 22 | services.kms.auth_key.grant_encrypt(function) 23 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/sns.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Input 3 | from ._base import TriggerBaseWidget, TriggerBaseContainer 4 | from ..text_area_theme import get_text_area 5 | 6 | 7 | class SNSContainer(TriggerBaseContainer): 8 | DEFAULT_CSS = """ 9 | SNSContainer { 10 | layout: grid; 11 | grid-size: 2 2; 12 | grid-rows: 5 10; 13 | grid-columns: 1fr 1fr; 14 | 15 | Input { 16 | column-span: 2; 17 | } 18 | } 19 | """ 20 | 21 | def compose(self) -> ComposeResult: 22 | yield Input(id="topic_arn") 23 | yield get_text_area("message") 24 | yield get_text_area("subject") 25 | 26 | 27 | class SNS(TriggerBaseWidget): 28 | service = "SNS" 29 | 30 | def render_left(self) -> ComposeResult: 31 | yield SNSContainer() 32 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/event_bridge.py: -------------------------------------------------------------------------------- 1 | import aws_cdk.aws_events as events 2 | import aws_cdk.aws_events_targets as targets 3 | 4 | from lambda_forge.trackers import trigger 5 | 6 | 7 | class EventBridge: 8 | def __init__(self, scope, context) -> None: 9 | 10 | # self.event_bridge = events.EventBus.from_event_bus_arn( 11 | # scope, 12 | # id="EventBridge", 13 | # event_bus_arn=context.resources["arns"]["event_bridge_arn"], 14 | # ) 15 | ... 16 | 17 | @trigger(service="event_bridge", trigger="rule_name", function="function") 18 | def schedule(self, rule_name, expression, function): 19 | events.Rule( 20 | self.scope, 21 | rule_name, 22 | schedule=events.Schedule.expression(expression), 23 | targets=[targets.LambdaFunction(handler=function)], 24 | ) 25 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/triggers.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Static, TabPane, TabbedContent 3 | from .s3 import S3 4 | from .sns import SNS 5 | from .sqs import SQS 6 | from .event_bridge import EventBridge 7 | from .api_gateway import ApiGateway 8 | 9 | 10 | class Triggers(Static): 11 | 12 | def compose(self) -> ComposeResult: 13 | with TabbedContent(): 14 | with TabPane("Api Gateway", id="api_gateway"): 15 | yield ApiGateway() 16 | 17 | with TabPane("EventBridge", id="event_bridge"): 18 | yield EventBridge() 19 | 20 | with TabPane("SNS", id="sns"): 21 | yield SNS() 22 | 23 | with TabPane("SQS", id="sqs"): 24 | yield SQS() 25 | 26 | with TabPane("S3", id="s3"): 27 | yield S3() 28 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_connection_id/config.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_iam as iam 2 | from infra.services import Services 3 | 4 | 5 | class SendConnectionIdConfig: 6 | def __init__(self, services: Services, context) -> None: 7 | 8 | function = services.aws_lambda.create_function( 9 | name="SendConnectionId", 10 | path="./functions/chat", 11 | description="Sends the connection id to the client when a connection is made", 12 | directory="send_connection_id", 13 | environment={ 14 | "POST_TO_CONNECTION_URL": context.resources["post_to_connection_url"] 15 | }, 16 | ) 17 | 18 | function.add_to_role_policy( 19 | iam.PolicyStatement( 20 | actions=["execute-api:ManageConnections"], 21 | resources=["arn:aws:execute-api:*:*:*"], 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /docs/examples/functions/images/mailer/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class MailerConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Mailer", 9 | path="./functions/images", 10 | description="Sends an email when an image enters the bucket", 11 | directory="mailer", 12 | layers=[services.layers.sm_utils_layer], 13 | environment={ 14 | "SMTP_HOST": "smtp.gmail.com", 15 | "SMTP_PORT": "465", 16 | "SECRET_NAME": services.secrets_manager.gmail_secret.secret_name, 17 | }, 18 | ) 19 | 20 | services.s3.images_bucket.grant_read(function) 21 | services.s3.create_trigger("images_bucket", function) 22 | 23 | services.secrets_manager.gmail_secret.grant_read(function) 24 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/connect/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import boto3 5 | 6 | 7 | def lambda_handler(event, context): 8 | 9 | # Retrieve the connection ID from the request context 10 | connection_id = event["requestContext"]["connectionId"] 11 | 12 | # Create a client for the AWS Lambda service 13 | lambda_client = boto3.client("lambda") 14 | 15 | # Retrieve the ARN of the target Lambda function from the environment variables 16 | TARGET_FUNCTION_ARN = os.environ.get("TARGET_FUNCTION_ARN") 17 | 18 | # Define the payload to pass to the target Lambda function 19 | payload = {"connection_id": connection_id} 20 | 21 | # Invoke the target Lambda function asynchronously 22 | lambda_client.invoke( 23 | FunctionName=TARGET_FUNCTION_ARN, 24 | InvocationType="Event", 25 | Payload=json.dumps(payload), 26 | ) 27 | 28 | return {"statusCode": 200} 29 | -------------------------------------------------------------------------------- /docs/examples/infra/services/secrets_manager.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_secretsmanager as sm 2 | 3 | 4 | class SecretsManager: 5 | def __init__(self, scope, context) -> None: 6 | 7 | self.gmail_secret = sm.Secret.from_secret_complete_arn( 8 | scope, 9 | id="GmailSecret", 10 | secret_complete_arn="arn:aws:secretsmanager:us-east-2:211125768252:secret:mailer-TfIeka", 11 | ) 12 | 13 | self.jwt_secret = sm.Secret.from_secret_complete_arn( 14 | scope, 15 | id="JwtSecret", 16 | secret_complete_arn="arn:aws:secretsmanager:us-east-2:211125768252:secret:jwt-yx2zBV", 17 | ) 18 | 19 | self.google_sso_secret = sm.Secret.from_secret_complete_arn( 20 | scope, 21 | id="GoogleSSOSecret", 22 | secret_complete_arn="arn:aws:secretsmanager:us-east-2:211125768252:secret:google-sso-cVVYlk", 23 | ) 24 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/s3.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Input, TextArea 3 | 4 | from lambda_forge.live.tui.ui.widgets.text_area_theme import get_text_area 5 | 6 | from ._base import TriggerBaseWidget, TriggerBaseContainer 7 | 8 | 9 | class S3Container(TriggerBaseContainer): 10 | DEFAULT_CSS = """ 11 | S3Container { 12 | layout: grid; 13 | grid-size: 2 2; 14 | grid-rows: 5 10; 15 | grid-columns: 1fr 1fr; 16 | 17 | TextArea { 18 | column-span: 2; 19 | } 20 | } 21 | """ 22 | 23 | def compose(self) -> ComposeResult: 24 | yield Input(id="bucket_name") 25 | yield Input(id="file_path") 26 | yield get_text_area("metadata") 27 | 28 | 29 | class S3(TriggerBaseWidget): 30 | service = "S3" 31 | 32 | def render_left(self) -> ComposeResult: 33 | yield S3Container() 34 | -------------------------------------------------------------------------------- /docs/examples/functions/chat/send_message/config.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_iam as iam 2 | from infra.services import Services 3 | 4 | 5 | class SendMessageConfig: 6 | def __init__(self, services: Services, context) -> None: 7 | 8 | function = services.aws_lambda.create_function( 9 | name="SendMessage", 10 | path="./functions/chat", 11 | description="Send messages to sender and recipient", 12 | directory="send_message", 13 | environment={ 14 | "POST_TO_CONNECTION_URL": context.resources["post_to_connection_url"], 15 | }, 16 | ) 17 | 18 | services.websockets.create_route("sendMessage", function) 19 | 20 | function.add_to_role_policy( 21 | iam.PolicyStatement( 22 | actions=["execute-api:ManageConnections"], 23 | resources=[f"arn:aws:execute-api:*:*:*"], 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/api/forge.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import List, Optional 3 | 4 | 5 | class ForgeAPI: 6 | _instance = None 7 | rows = [] 8 | 9 | def __new__(cls, *args, **kwargs): 10 | if cls._instance is None: 11 | cls._instance = super().__new__(cls, *args, **kwargs) 12 | return cls._instance 13 | 14 | def get_servers(self) -> List[List[Optional[str]]]: 15 | return self.rows 16 | 17 | def set_functions(self, functions) -> None: 18 | for function in functions: 19 | row = ( 20 | function["name"], 21 | function["service"], 22 | function["type"], 23 | function["trigger"], 24 | ) 25 | self.rows.append(row) 26 | 27 | def get_log_file_path(self) -> Path: 28 | path = Path("live.log") 29 | if not path.exists(): 30 | path.touch() 31 | 32 | return path 33 | -------------------------------------------------------------------------------- /docs/demo/infra/services/sns.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda_event_sources 2 | from aws_cdk import aws_sns as sns 3 | 4 | from lambda_forge.trackers import invoke, trigger 5 | 6 | 7 | class SNS: 8 | def __init__(self, scope, context) -> None: 9 | 10 | self.hello_world_topic = sns.Topic.from_topic_arn( 11 | scope, 12 | id="HelloWorldTopic", 13 | topic_arn=context.resources["arns"]["hello_world_topic"], 14 | ) 15 | 16 | @trigger(service="sns", trigger="topic", function="function") 17 | def create_trigger(self, topic, function): 18 | topic = getattr(self, topic) 19 | sns_subscription = aws_lambda_event_sources.SnsEventSource(topic) 20 | function.add_event_source(sns_subscription) 21 | 22 | @invoke(service="sns", resource="topic", function="function") 23 | def grant_publish(self, topic, function): 24 | topic = getattr(self, topic) 25 | topic.grant_publish(function) 26 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/sns.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda_event_sources 2 | from aws_cdk import aws_sns as sns 3 | 4 | from lambda_forge.trackers import invoke, trigger 5 | 6 | 7 | class SNS: 8 | def __init__(self, scope, context) -> None: 9 | 10 | # self.sns_topic = sns.Topic.from_topic_arn( 11 | # scope, 12 | # id="SNSTopic", 13 | # topic_arn=context.resources["arns"]["sns_topic_arn"], 14 | # ) 15 | ... 16 | 17 | @trigger(service="sns", trigger="topic", function="function") 18 | def create_trigger(self, topic, function): 19 | topic = getattr(self, topic) 20 | sns_subscription = aws_lambda_event_sources.SnsEventSource(topic) 21 | function.add_event_source(sns_subscription) 22 | 23 | @invoke(service="sns", resource="topic", function="function") 24 | def grant_publish(self, topic, function): 25 | topic = getattr(self, topic) 26 | topic.grant_publish(function) 27 | -------------------------------------------------------------------------------- /docs/examples/infra/services/__init__.py: -------------------------------------------------------------------------------- 1 | from infra.services.api_gateway import APIGateway 2 | from infra.services.aws_lambda import Lambda 3 | from infra.services.cognito import Cognito 4 | from infra.services.dynamodb import DynamoDB 5 | from infra.services.kms import KMS 6 | from infra.services.layers import Layers 7 | from infra.services.s3 import S3 8 | from infra.services.secrets_manager import SecretsManager 9 | from infra.services.websockets import Websockets 10 | 11 | 12 | class Services: 13 | def __init__(self, scope, context) -> None: 14 | self.api_gateway = APIGateway(scope, context) 15 | self.aws_lambda = Lambda(scope, context) 16 | self.dynamodb = DynamoDB(scope, context) 17 | self.s3 = S3(scope, context) 18 | self.secrets_manager = SecretsManager(scope, context) 19 | self.layers = Layers(scope) 20 | self.kms = KMS(scope, context) 21 | self.websockets = Websockets(scope, context) 22 | self.cognito = Cognito(scope, context) 23 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/sqs.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda_event_sources 2 | from aws_cdk import aws_sqs as sqs 3 | 4 | from lambda_forge.trackers import invoke, trigger 5 | 6 | 7 | class SQS: 8 | def __init__(self, scope, context) -> None: 9 | 10 | # self.sqs = sqs.Queue.from_queue_arn( 11 | # scope, 12 | # "SQS", 13 | # queue_arn=context.resources["arns"]["sqs_arn"], 14 | # ) 15 | ... 16 | 17 | @trigger(service="sqs", trigger="queue", function="function") 18 | def create_trigger(self, queue, function): 19 | queue = getattr(self, queue) 20 | event_source = aws_lambda_event_sources.SqsEventSource(queue) 21 | function.add_event_source(event_source) 22 | queue.grant_consume_messages(function) 23 | 24 | @invoke(service="sqs", resource="queue", function="function") 25 | def grant_send_messages(self, queue, function): 26 | queue = getattr(self, queue) 27 | queue.grant_send_messages(function) 28 | -------------------------------------------------------------------------------- /docs/examples/infra/services/s3.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_s3 as s3 2 | from aws_cdk import aws_s3_notifications 3 | 4 | from lambda_forge.trackers import invoke, trigger 5 | 6 | 7 | class S3: 8 | def __init__(self, scope, context) -> None: 9 | 10 | self.images_bucket = s3.Bucket.from_bucket_arn( 11 | scope, 12 | "ImagesBucket", 13 | bucket_arn=context.resources["arns"]["images_bucket"], 14 | ) 15 | 16 | @trigger(service="s3", trigger="bucket", function="function") 17 | def create_trigger(self, bucket, function, event=s3.EventType.OBJECT_CREATED): 18 | bucket = getattr(self, bucket) 19 | notifications = aws_s3_notifications.LambdaDestination(function) 20 | bucket.add_event_notification(event, notifications) 21 | bucket.grant_read(function) 22 | 23 | @invoke(service="s3", resource="bucket", function="function") 24 | def grant_write(self, bucket, function): 25 | bucket = getattr(self, bucket) 26 | bucket.grant_write(function) 27 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/s3.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_s3 as s3 2 | from aws_cdk import aws_s3_notifications 3 | 4 | from lambda_forge.trackers import invoke, trigger 5 | 6 | 7 | class S3: 8 | def __init__(self, scope, context) -> None: 9 | 10 | # self.s3 = s3.Bucket.from_bucket_arn( 11 | # scope, 12 | # "S3", 13 | # bucket_arn=context.resources["arns"]["s3_arn"], 14 | # ) 15 | ... 16 | 17 | @trigger(service="s3", trigger="bucket", function="function") 18 | def create_trigger(self, bucket, function, event=s3.EventType.OBJECT_CREATED): 19 | bucket = getattr(self, bucket) 20 | notifications = aws_s3_notifications.LambdaDestination(function) 21 | bucket.add_event_notification(event, notifications) 22 | bucket.grant_read(function) 23 | 24 | @invoke(service="s3", resource="bucket", function="function") 25 | def grant_write(self, bucket, function): 26 | bucket = getattr(self, bucket) 27 | bucket.grant_write(function) 28 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.th.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}}); -------------------------------------------------------------------------------- /docs/examples/docs/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class DocsConfig: 5 | def __init__(self, services: Services) -> None: 6 | # Swagger at /swagger 7 | services.api_gateway.create_docs( 8 | endpoint="/swagger", artifact="swagger", public=True 9 | ) 10 | 11 | # Redoc at /redoc 12 | services.api_gateway.create_docs( 13 | endpoint="/redoc", artifact="redoc", public=True 14 | ) 15 | 16 | # Architecture Diagram at /diagram 17 | services.api_gateway.create_docs( 18 | endpoint="/diagram", artifact="diagram", public=True, stages=["Prod"] 19 | ) 20 | 21 | # Tests Report at /tests 22 | services.api_gateway.create_docs( 23 | endpoint="/tests", artifact="tests", public=True, stages=["Staging"] 24 | ) 25 | 26 | # Coverage Report at /coverage 27 | services.api_gateway.create_docs( 28 | endpoint="/coverage", artifact="coverage", public=True, stages=["Staging"] 29 | ) 30 | -------------------------------------------------------------------------------- /.github/workflows/actions.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Push 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build-and-push: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Login to Amazon ECR 20 | run: | 21 | aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/w1u4u5r2 24 | 25 | - name: Build Docker image 26 | run: docker build -t lambda-forge ecr/. 27 | 28 | - name: Tag Docker image 29 | run: docker tag lambda-forge:latest public.ecr.aws/w1u4u5r2/lambda-forge:latest 30 | 31 | - name: Push Docker image to ECR 32 | run: docker push public.ecr.aws/w1u4u5r2/lambda-forge:latest 33 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/like_post/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from dataclasses import dataclass 4 | 5 | import boto3 6 | 7 | 8 | @dataclass 9 | class Path: 10 | post_id: str 11 | 12 | 13 | @dataclass 14 | class Input: 15 | pass 16 | 17 | 18 | @dataclass 19 | class Output: 20 | pass 21 | 22 | 23 | def lambda_handler(event, context): 24 | 25 | dynamodb = boto3.resource("dynamodb") 26 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 27 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 28 | 29 | post_id = event.get("pathParameters", {}).get("post_id") 30 | 31 | email = event["requestContext"]["authorizer"]["email"] 32 | 33 | post = posts_table.get_item(Key={"PK": post_id}).get("Item") 34 | likes = post.get("likes", []) 35 | 36 | if email in likes: 37 | likes.remove(email) 38 | else: 39 | likes.append(email) 40 | 41 | post["likes"] = likes 42 | posts_table.put_item(Item={**post}) 43 | 44 | return {"statusCode": 204, "headers": {"Access-Control-Allow-Origin": "*"}} 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Guilherme Alves Pimenta 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/screens/index.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult, on 2 | from textual.screen import Screen 3 | from textual.widgets import TabbedContent, TabPane 4 | from lambda_forge.logs.tui.api.forge_logs import ForgeLogsAPI 5 | from lambda_forge.logs.tui.ui.widgets.cloudwatch_log import CloudWatchLogs 6 | from ..widgets import ForgeLogsHeader 7 | 8 | 9 | class Index(Screen): 10 | DEFAULT_CSS = """ 11 | Index { 12 | layout: grid; 13 | grid-size: 1 2; 14 | grid-rows: 4 1fr; 15 | } 16 | """ 17 | 18 | @property 19 | def logs_api(self) -> ForgeLogsAPI: 20 | return self.app.logs_api 21 | 22 | def compose(self) -> ComposeResult: 23 | yield ForgeLogsHeader() 24 | with TabbedContent(id="cloud_watch_logs"): 25 | for log_group in self.logs_api.get_lambdas(): 26 | with TabPane(log_group.group): 27 | yield CloudWatchLogs(log_group) 28 | 29 | @on(TabbedContent.TabActivated) 30 | def _tab_activated(self, event: TabbedContent.TabActivated): 31 | event.pane.query_one(CloudWatchLogs).reset_logs() 32 | -------------------------------------------------------------------------------- /docs/examples/functions/auth/signin/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class SigninConfig: 5 | def __init__(self, services: Services) -> None: 6 | 7 | function = services.aws_lambda.create_function( 8 | name="Signin", 9 | path="./functions/auth", 10 | description="Authenticate user login by verifying email and password against stored credentials", 11 | directory="signin", 12 | layers=[services.layers.sm_utils_layer, services.layers.pyjwt_layer], 13 | environment={ 14 | "AUTH_TABLE_NAME": services.dynamodb.auth_table.table_name, 15 | "KMS_KEY_ID": services.kms.auth_key.key_id, 16 | "JWT_SECRET_NAME": services.secrets_manager.jwt_secret.secret_name, 17 | }, 18 | ) 19 | 20 | services.api_gateway.create_endpoint("POST", "/signin", function, public=True) 21 | 22 | services.dynamodb.auth_table.grant_read_data(function) 23 | 24 | services.kms.auth_key.grant_decrypt(function) 25 | 26 | services.secrets_manager.jwt_secret.grant_read(function) 27 | -------------------------------------------------------------------------------- /ecr/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | 3 | WORKDIR /lambda-forge 4 | 5 | COPY . /lambda-forge 6 | 7 | # Install nvm with Node.js and npm 8 | ENV NODE_VERSION=18.18.0 9 | RUN apt-get update \ 10 | && apt-get install -y curl jq 11 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 12 | ENV NVM_DIR=/root/.nvm 13 | RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION} 14 | RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION} 15 | RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION} 16 | ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}" 17 | RUN node --version 18 | RUN npm --version 19 | 20 | # Install Node.js dependencies 21 | RUN apt-get update && apt-get install -y gnupg \ 22 | && apt-get clean && rm -rf /var/lib/apt/lists/* \ 23 | && npm install -g aws-cdk redoc-cli cdk-assets@2 24 | 25 | RUN node --version 26 | RUN npm --version 27 | 28 | # Install Python dependencies 29 | RUN pip install --upgrade pip \ 30 | && pip install pyyaml pytest-html coverage awscli boto3==1.33.2 botocore==1.33.2 \ 31 | && pip install -r base-requirements.txt \ 32 | && pip install lambda-forge 33 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.hy.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}}); -------------------------------------------------------------------------------- /docs/demo/infra/stacks/lambda_stack.py: -------------------------------------------------------------------------------- 1 | from authorizers.secret.config import SecretAuthorizerConfig 2 | from aws_cdk import Stack 3 | from constructs import Construct 4 | from docs.config import DocsConfig 5 | from functions.custom.config import CustomConfig 6 | from functions.external.config import ExternalConfig 7 | from functions.hello_world.config import HelloWorldConfig 8 | from functions.private.config import PrivateConfig 9 | from infra.services import Services 10 | 11 | 12 | class LambdaStack(Stack): 13 | def __init__(self, scope: Construct, context, **kwargs) -> None: 14 | 15 | super().__init__(scope, f"{context.name}-Lambda-Stack", **kwargs) 16 | 17 | self.services = Services(self, context) 18 | 19 | # Authorizers 20 | SecretAuthorizerConfig(self.services) 21 | 22 | # Docs 23 | DocsConfig(self.services) 24 | 25 | # HelloWorld 26 | HelloWorldConfig(self.services) 27 | 28 | # # Private 29 | PrivateConfig(self.services) 30 | 31 | # Custom 32 | CustomConfig(self.services) 33 | 34 | # # External 35 | ExternalConfig(self.services) 36 | -------------------------------------------------------------------------------- /docs/examples/authorizers/sso/main.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | 3 | 4 | def lambda_handler(event, context): 5 | 6 | token = event["headers"].get("authorization") 7 | try: 8 | decoded_token = jwt.decode(token, options={"verify_signature": False}) 9 | effect = "allow" 10 | picture = decoded_token.get("picture") 11 | first_name = decoded_token.get("given_name") 12 | last_name = decoded_token.get("family_name") 13 | email = decoded_token.get("email") 14 | context = { 15 | "picture": picture, 16 | "first_name": first_name, 17 | "last_name": last_name, 18 | "email": email, 19 | } 20 | 21 | except: 22 | effect = "deny" 23 | context = {} 24 | 25 | return { 26 | "context": context, 27 | "policyDocument": { 28 | "Version": "2012-10-17", 29 | "Statement": [ 30 | { 31 | "Action": "execute-api:Invoke", 32 | "Effect": effect, 33 | "Resource": event["methodArn"], 34 | } 35 | ], 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /docs/docs/license/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Guilherme Alves Pimenta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/triggers/api_gateway.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.widgets import Input, Select, TextArea 3 | 4 | from lambda_forge.live.tui.ui.widgets.text_area_theme import get_text_area 5 | 6 | from ._base import TriggerBaseWidget, TriggerBaseContainer 7 | 8 | 9 | class ApiGatewayContainer(TriggerBaseContainer): 10 | DEFAULT_CSS = """ 11 | ApiGatewayContainer { 12 | layout: grid; 13 | grid-size: 3 2; 14 | grid-rows: 5 10; 15 | grid-columns: 1fr 1fr 1fr; 16 | } 17 | 18 | ApiGatewayContainer > #url { 19 | column-span: 2; 20 | } 21 | """ 22 | 23 | METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"] 24 | 25 | def compose(self) -> ComposeResult: 26 | yield Select(options=[(i, i) for i in self.METHODS], id="method") 27 | yield Input(id="url") 28 | 29 | yield get_text_area("query") 30 | yield get_text_area("body") 31 | yield get_text_area("headers") 32 | 33 | 34 | class ApiGateway(TriggerBaseWidget): 35 | service = "Api Gateway" 36 | 37 | def render_left(self) -> ComposeResult: 38 | yield ApiGatewayContainer() 39 | -------------------------------------------------------------------------------- /ecr/validate_integration_tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | 5 | def validate_tests(endpoints, tested_endpoints): 6 | for endpoint in endpoints: 7 | new_endpoint = {"endpoint": endpoint["trigger"], "method": endpoint["method"]} 8 | if new_endpoint not in tested_endpoints: 9 | raise Exception( 10 | f"Endpoint {endpoint['trigger']} with method {endpoint['method']} should have at least 1 integration test." 11 | ) 12 | 13 | 14 | if __name__ == "__main__": 15 | 16 | tested_endpoints = [] 17 | if os.path.exists("tested_endpoints.txt"): 18 | with open("tested_endpoints.txt", "r") as jl: 19 | json_list = jl.read().split("|")[:-1] 20 | tested_endpoints = [json.loads(json_str) for json_str in json_list] 21 | 22 | with open("functions.json", "r") as json_file: 23 | functions = json.load(json_file) 24 | endpoints = [] 25 | for function in functions: 26 | for trigger in function["triggers"]: 27 | if trigger["service"] == "api_gateway": 28 | endpoints.append(trigger) 29 | 30 | validate_tests(endpoints, tested_endpoints) 31 | -------------------------------------------------------------------------------- /docs/demo/docs/config.py: -------------------------------------------------------------------------------- 1 | from infra.services import Services 2 | 3 | 4 | class DocsConfig: 5 | def __init__(self, services: Services) -> None: 6 | # Swagger at /swagger 7 | services.api_gateway.create_docs( 8 | endpoint="/swagger", artifact="swagger", public=True 9 | ) 10 | 11 | # Redoc at /redoc 12 | services.api_gateway.create_docs( 13 | endpoint="/redoc", artifact="redoc", public=True 14 | ) 15 | 16 | # Architecture Diagram at /diagram 17 | services.api_gateway.create_docs( 18 | endpoint="/diagram", artifact="diagram", public=True, stages=["Prod"] 19 | ) 20 | 21 | # Tests Report at /tests 22 | services.api_gateway.create_docs( 23 | endpoint="/tests", artifact="tests", public=True, stages=["Staging"] 24 | ) 25 | 26 | # Coverage Report at /coverage 27 | services.api_gateway.create_docs( 28 | endpoint="/coverage", artifact="coverage", public=True, stages=["Staging"] 29 | ) 30 | 31 | # Wiki at /wiki 32 | # Use the Wiki's title as artifact 33 | services.api_gateway.create_docs(endpoint="/wiki", artifact="Wiki", public=True) 34 | -------------------------------------------------------------------------------- /docs/examples/authorizers/jwt/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import jwt 4 | import sm_utils 5 | 6 | 7 | def lambda_handler(event, context): 8 | 9 | # Extract the JWT token from the event 10 | token = event["headers"].get("authorization") 11 | 12 | # Retrieve the JWT secret from Secrets Manager 13 | JWT_SECRET_NAME = os.environ.get("JWT_SECRET_NAME") 14 | JWT_SECRET = sm_utils.get_secret(JWT_SECRET_NAME) 15 | 16 | try: 17 | # Decode the JWT token 18 | decoded_token = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) 19 | effect = "allow" 20 | email = decoded_token.get("email") 21 | except: 22 | effect = "deny" 23 | email = None 24 | 25 | # Set the decoded email as context 26 | context = {"email": email} 27 | 28 | # Allow access with the user's email 29 | return { 30 | "context": context, 31 | "policyDocument": { 32 | "Version": "2012-10-17", 33 | "Statement": [ 34 | { 35 | "Action": "execute-api:Invoke", 36 | "Effect": effect, 37 | "Resource": event["methodArn"], 38 | } 39 | ], 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | Lambda Forge is committed to fostering a welcoming and inclusive environment for all contributors. This Code of Conduct outlines our expectations for behavior in our community. 5 | 6 | ## 2. Expected Behavior 7 | - Be respectful, considerate, and constructive. 8 | - Use inclusive and welcoming language. 9 | - Be open to collaboration and constructive feedback. 10 | - Respect differing viewpoints and experiences. 11 | - Show empathy towards others. 12 | 13 | ## 3. Unacceptable Behavior 14 | - Harassment, discrimination, or offensive comments based on race, gender, religion, or any protected characteristic. 15 | - Personal attacks, trolling, or disruptive behavior. 16 | - Publishing private information without consent. 17 | - Unethical or illegal activities. 18 | 19 | ## 4. Enforcement 20 | Violations of this Code of Conduct may result in warnings, temporary bans, or permanent bans at the discretion of the project maintainers. 21 | 22 | ## 5. Reporting Issues 23 | If you experience or witness any unacceptable behavior, please report it to the maintainers at `guialvespimenta27@gmail.com`. 24 | 25 | By participating in this project, you agree to abide by this Code of Conduct. 26 | 27 | -------------------------------------------------------------------------------- /lambda_forge/builders/services/dynamodb.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_dynamodb as dynamodb 2 | from aws_cdk import aws_lambda as lambda_ 3 | from aws_cdk import aws_lambda_event_sources as event_source 4 | 5 | from lambda_forge.trackers import invoke, trigger 6 | 7 | 8 | class DynamoDB: 9 | def __init__(self, scope, context) -> None: 10 | 11 | # self.dynamo = dynamodb.Table.from_table_arn( 12 | # scope, 13 | # "Dynamo", 14 | # context.resources["arns"]["dynamo_arn"], 15 | # ) 16 | ... 17 | 18 | @trigger(service="dynamodb", trigger="table", function="function") 19 | def create_trigger(self, table: str, function: lambda_.Function) -> None: 20 | table_instance = getattr(self, table) 21 | dynamo_event_stream = event_source.DynamoEventSource( 22 | table_instance, starting_position=lambda_.StartingPosition.TRIM_HORIZON 23 | ) 24 | function.add_event_source(dynamo_event_stream) 25 | 26 | @invoke(service="dynamodb", resource="table", function="function") 27 | def grant_write(self, table: str, function: lambda_.Function) -> None: 28 | table_instance = getattr(self, table) 29 | table_instance.grant_write_data(function) 30 | -------------------------------------------------------------------------------- /docs/demo/infra/services/aws_lambda.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import Duration 2 | from aws_cdk.aws_lambda import Code, Function, Runtime 3 | 4 | from lambda_forge.path import Path 5 | from lambda_forge.trackers import function 6 | 7 | 8 | class Lambda: 9 | def __init__(self, scope, context) -> None: 10 | self.functions = {} 11 | self.scope = scope 12 | self.context = context 13 | 14 | @function 15 | def create_function( 16 | self, 17 | name, 18 | path, 19 | description, 20 | directory=None, 21 | layers=[], 22 | environment={}, 23 | memory_size=128, 24 | runtime=Runtime.PYTHON_3_9, 25 | timeout=1, 26 | ): 27 | 28 | function = Function( 29 | scope=self.scope, 30 | id=name, 31 | description=description, 32 | function_name=self.context.create_id(name), 33 | runtime=runtime, 34 | handler=Path.handler(directory), 35 | environment=environment, 36 | code=Code.from_asset(path=Path.function(path)), 37 | layers=layers, 38 | timeout=Duration.minutes(timeout), 39 | memory_size=memory_size, 40 | ) 41 | 42 | self.functions[name] = function 43 | return function 44 | -------------------------------------------------------------------------------- /docs/examples/infra/services/aws_lambda.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import Duration 2 | from aws_cdk.aws_lambda import Code, Function, Runtime 3 | 4 | from lambda_forge.path import Path 5 | from lambda_forge.trackers import function 6 | 7 | 8 | class Lambda: 9 | def __init__(self, scope, context) -> None: 10 | self.functions = {} 11 | self.scope = scope 12 | self.context = context 13 | 14 | @function 15 | def create_function( 16 | self, 17 | name, 18 | path, 19 | description, 20 | directory=None, 21 | layers=[], 22 | environment={}, 23 | memory_size=128, 24 | runtime=Runtime.PYTHON_3_9, 25 | timeout=1, 26 | ): 27 | 28 | function = Function( 29 | scope=self.scope, 30 | id=name, 31 | description=description, 32 | function_name=self.context.create_id(name), 33 | runtime=runtime, 34 | handler=Path.handler(directory), 35 | environment=environment, 36 | code=Code.from_asset(path=Path.function(path)), 37 | layers=layers, 38 | timeout=Duration.minutes(timeout), 39 | memory_size=memory_size, 40 | ) 41 | 42 | self.functions[name] = function 43 | return function 44 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/services/aws_lambda.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import Duration 2 | from aws_cdk.aws_lambda import Code, Function, Runtime 3 | 4 | from lambda_forge.path import Path 5 | from lambda_forge.trackers import function 6 | 7 | 8 | class Lambda: 9 | def __init__(self, scope, context) -> None: 10 | self.functions = {} 11 | self.scope = scope 12 | self.context = context 13 | 14 | @function 15 | def create_function( 16 | self, 17 | name, 18 | path, 19 | description, 20 | directory=None, 21 | layers=[], 22 | environment={}, 23 | memory_size=128, 24 | runtime=Runtime.PYTHON_3_9, 25 | timeout=1, 26 | ): 27 | 28 | function = Function( 29 | scope=self.scope, 30 | id=name, 31 | description=description, 32 | function_name=self.context.create_id(name), 33 | runtime=runtime, 34 | handler=Path.handler(directory), 35 | environment=environment, 36 | code=Code.from_asset(path=Path.function(path)), 37 | layers=layers, 38 | timeout=Duration.minutes(timeout), 39 | memory_size=memory_size, 40 | ) 41 | 42 | self.functions[name] = function 43 | return function 44 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/shortener/main.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import os 4 | from dataclasses import dataclass 5 | 6 | import boto3 7 | 8 | 9 | @dataclass 10 | class Input: 11 | url: str 12 | 13 | 14 | @dataclass 15 | class Output: 16 | short_url: str 17 | 18 | 19 | def lambda_handler(event, context): 20 | # Retrieve DynamoDB table name and the Base URL from environment variables. 21 | URLS_TABLE_NAME = os.environ.get("URLS_TABLE_NAME") 22 | BASE_URL = os.environ.get("BASE_URL") 23 | 24 | # Initialize DynamoDB resource. 25 | dynamodb = boto3.resource("dynamodb") 26 | 27 | # Reference the specified DynamoDB table. 28 | urls_table = dynamodb.Table(URLS_TABLE_NAME, "Dev-URLs") 29 | 30 | # Parse the URL from the incoming event's body. 31 | body = json.loads(event["body"]) 32 | original_url = body["url"] 33 | 34 | # Generate a URL hash. 35 | hash_object = hashlib.sha256(original_url.encode()) 36 | url_id = hash_object.hexdigest()[:6] 37 | 38 | # Store the mapping in DynamoDB. 39 | urls_table.put_item(Item={"PK": url_id, "original_url": original_url}) 40 | 41 | # Construct the shortened URL. 42 | short_url = f"{BASE_URL}/{url_id}" 43 | 44 | # Return success response. 45 | return {"statusCode": 200, "body": json.dumps({"short_url": short_url})} 46 | -------------------------------------------------------------------------------- /lambda_forge/logs/tui/ui/widgets/cloudwatch_single_log.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from rich.console import RenderableType 3 | from rich.table import Table 4 | from textual.widgets.option_list import Option 5 | from ...api.forge_logs import CloudWatchLog 6 | 7 | 8 | class CloudWatchSingleLog(Option): 9 | 10 | def __init__(self, log: CloudWatchLog): 11 | super().__init__("") 12 | self.log = log 13 | self.tall = False 14 | self.refresh_prompt() 15 | 16 | def refresh_prompt(self): 17 | table = Table.grid(padding=(0, 1)) 18 | table.add_column("timestamp", width=25) 19 | table.add_column("log_type", width=10) 20 | table.add_column("message") 21 | 22 | timestamp = datetime.fromtimestamp(self.log.timestamp).strftime( 23 | "%Y-%m-%d (%H:%M)" 24 | ) 25 | table.add_row(timestamp, self.log.log_type.value, self.log.message) 26 | table.add_row() 27 | self._set_prompt(table) 28 | 29 | def _set_prompt(self, prompt: RenderableType): 30 | self.set_prompt(prompt) 31 | # 32 | # self.set_prompt( 33 | # Panel( 34 | # prompt, 35 | # box=box.ROUNDED, 36 | # ) 37 | # ) 38 | 39 | def toggle_display(self): 40 | self.tall = not self.tall 41 | self.refresh_prompt() 42 | -------------------------------------------------------------------------------- /lambda_forge/stacks/minimal/stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | 11 | 12 | @context(minimal=True) 13 | class Stack(cdk.Stack): 14 | def __init__(self, scope: Construct, context, **kwargs) -> None: 15 | super().__init__(scope, context.create_id("Stack"), **kwargs) 16 | 17 | source = CodePipelineSource.git_hub( 18 | f"{context.repo['owner']}/{context.repo['name']}", "main" 19 | ) 20 | 21 | pipeline = pipelines.CodePipeline( 22 | self, 23 | "Pipeline", 24 | pipeline_name=context.create_id("Pipeline"), 25 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 26 | code_build_defaults=pipelines.CodeBuildOptions( 27 | build_environment=codebuild.BuildEnvironment( 28 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 29 | ECR.LATEST 30 | ), 31 | ) 32 | ), 33 | ) 34 | 35 | pipeline.add_stage(DeployStage(self, context)) 36 | -------------------------------------------------------------------------------- /lambda_forge/stacks/no_docs/dev_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines as pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | 11 | 12 | @context(stage="Dev", resources="dev") 13 | class DevStack(cdk.Stack): 14 | def __init__(self, scope: Construct, context, **kwargs) -> None: 15 | super().__init__(scope, context.create_id("Stack"), **kwargs) 16 | 17 | source = CodePipelineSource.git_hub( 18 | f"{context.repo['owner']}/{context.repo['name']}", "dev" 19 | ) 20 | 21 | pipeline = pipelines.CodePipeline( 22 | self, 23 | "Pipeline", 24 | pipeline_name=context.create_id("Pipeline"), 25 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 26 | code_build_defaults=pipelines.CodeBuildOptions( 27 | build_environment=codebuild.BuildEnvironment( 28 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 29 | ECR.LATEST 30 | ), 31 | ) 32 | ), 33 | ) 34 | 35 | pipeline.add_stage(DeployStage(self, context)) 36 | -------------------------------------------------------------------------------- /docs/demo/authorizers/secret/main.py: -------------------------------------------------------------------------------- 1 | def lambda_handler(event, context): 2 | 3 | # ATTENTION: The example provided below is strictly for demonstration purposes and should NOT be deployed in a production environment. 4 | # It's crucial to develop and integrate your own robust authorization mechanism tailored to your application's security requirements. 5 | # To utilize the example authorizer as a temporary placeholder, ensure to include the following header in your requests: 6 | 7 | # Header: 8 | # secret: CRMdDRMA4iW4xo9l38pACls7zsHYfp8T7TLXtucysb2lB5XBVFn8 9 | 10 | # Remember, security is paramount. This placeholder serves as a guide to help you understand the kind of information your custom authorizer should authenticate. 11 | # Please replace it with your secure, proprietary logic before going live. Happy coding! 12 | 13 | secret = event["headers"].get("secret") 14 | SECRET = "CRMdDRMA4iW4xo9l38pACls7zsHYfp8T7TLXtucysb2lB5XBVFn8" 15 | 16 | effect = "allow" if secret == SECRET else "deny" 17 | 18 | policy = { 19 | "policyDocument": { 20 | "Version": "2012-10-17", 21 | "Statement": [ 22 | { 23 | "Action": "execute-api:Invoke", 24 | "Effect": effect, 25 | "Resource": event["methodArn"], 26 | } 27 | ], 28 | }, 29 | } 30 | return policy 31 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/screens/index.py: -------------------------------------------------------------------------------- 1 | from textual.app import ComposeResult 2 | from textual.binding import Binding 3 | from textual.screen import Screen 4 | from textual.widgets import Footer, TabPane, TabbedContent 5 | 6 | from lambda_forge.live.tui.api.forge import ForgeAPI 7 | from ..widgets import ForgeHeader, ServerTable, LogStream, Triggers 8 | 9 | forge = ForgeAPI() 10 | 11 | 12 | class IndexScreen(Screen): 13 | DEFAULT_CSS = """ 14 | IndexScreen { 15 | layout: grid; 16 | grid-size: 1 2; 17 | grid-rows: 4 1fr; 18 | } 19 | """ 20 | 21 | BINDINGS = [ 22 | Binding("s", "move_to_tab('server')", "Server"), 23 | Binding("l", "move_to_tab('logs')", "Server"), 24 | Binding("t", "move_to_tab('triggers')", "Server"), 25 | ] 26 | 27 | def action_move_to_tab(self, tab: str): 28 | self.tabbed_container.active = tab 29 | 30 | def compose(self) -> ComposeResult: 31 | yield ForgeHeader() 32 | with TabbedContent(initial="server") as t: 33 | self.tabbed_container = t 34 | 35 | with TabPane("Server", id="server"): 36 | yield ServerTable() 37 | 38 | with TabPane("Logs", id="logs"): 39 | yield LogStream(forge.get_log_file_path()) 40 | 41 | with TabPane("Triggers", id="triggers"): 42 | yield Triggers() 43 | 44 | yield Footer() 45 | -------------------------------------------------------------------------------- /docs/demo/authorizers/secret/unit.py: -------------------------------------------------------------------------------- 1 | from .main import lambda_handler 2 | 3 | 4 | def test_authorizer_should_pass_with_correct_secret(): 5 | 6 | event = { 7 | "headers": {"secret": "CRMdDRMA4iW4xo9l38pACls7zsHYfp8T7TLXtucysb2lB5XBVFn8"}, 8 | "methodArn": "arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/resource-path", 9 | } 10 | response = lambda_handler(event, None) 11 | 12 | assert response == { 13 | "policyDocument": { 14 | "Version": "2012-10-17", 15 | "Statement": [ 16 | { 17 | "Action": "execute-api:Invoke", 18 | "Effect": "allow", 19 | "Resource": event["methodArn"], 20 | } 21 | ], 22 | }, 23 | } 24 | 25 | 26 | def test_authorizer_should_fail_with_invalid_secret(): 27 | 28 | event = { 29 | "headers": {"secret": "INVALID-SECRET"}, 30 | "methodArn": "arn:aws:execute-api:us-east-1:123456789012:api-id/stage/GET/resource-path", 31 | } 32 | response = lambda_handler(event, None) 33 | 34 | assert response == { 35 | "policyDocument": { 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Action": "execute-api:Invoke", 40 | "Effect": "deny", 41 | "Resource": event["methodArn"], 42 | } 43 | ], 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /docs/examples/functions/urls/redirect/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from dataclasses import dataclass 4 | 5 | import boto3 6 | 7 | 8 | @dataclass 9 | class Path: 10 | url_id: str 11 | 12 | 13 | @dataclass 14 | class Input: 15 | pass 16 | 17 | 18 | @dataclass 19 | class Output: 20 | pass 21 | 22 | 23 | def lambda_handler(event, context): 24 | 25 | # Retrieve DynamoDB table name from environment variables. 26 | URLS_TABLE_NAME = os.environ.get("URLS_TABLE_NAME") 27 | 28 | # Initialize DynamoDB resource and table reference. 29 | dynamodb = boto3.resource("dynamodb") 30 | urls_table = dynamodb.Table(URLS_TABLE_NAME) 31 | 32 | # Extract shortened URL identifier from path parameters. 33 | short_url = event["pathParameters"]["url_id"] 34 | 35 | # Retrieve the original URL using the shortened identifier. 36 | response = urls_table.get_item(Key={"PK": short_url}) 37 | original_url = response.get("Item", {}).get("original_url") 38 | 39 | # Return 404 if no URL is found for the identifier. 40 | if original_url is None: 41 | return {"statusCode": 404, "body": json.dumps({"message": "URL not found"})} 42 | 43 | # Ensure URL starts with "http://" or "https://". 44 | if not original_url.startswith("http"): 45 | original_url = f"http://{original_url}" 46 | 47 | # Redirect to the original URL with a 301 Moved Permanently response. 48 | return {"statusCode": 301, "headers": {"Location": original_url}} 49 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/delete_comment/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from dataclasses import dataclass 4 | 5 | import boto3 6 | 7 | 8 | @dataclass 9 | class Path: 10 | post_id: str 11 | 12 | 13 | @dataclass 14 | class Input: 15 | comment_id: str 16 | 17 | 18 | @dataclass 19 | class Output: 20 | pass 21 | 22 | 23 | def lambda_handler(event, context): 24 | 25 | post_id = event.get("pathParameters", {}).get("post_id") 26 | 27 | body = json.loads(event.get("body")) 28 | comment_id = body.get("comment_id") 29 | 30 | email = event["requestContext"]["authorizer"]["email"] 31 | 32 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 33 | dynamodb = boto3.resource("dynamodb") 34 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 35 | 36 | post = posts_table.get_item(Key={"PK": post_id}).get("Item") 37 | comments = post.get("comments", []) 38 | 39 | comment = next( 40 | (comment for comment in comments if comment["comment_id"] == comment_id), None 41 | ) 42 | if comment["email"] != email: 43 | return { 44 | "statusCode": 403, 45 | "body": {"message": "You are not allowed to delete this comment"}, 46 | "headers": {"Access-Control-Allow-Origin": "*"}, 47 | } 48 | 49 | post["comments"] = [c for c in comments if c["comment_id"] != comment_id] 50 | 51 | posts_table.put_item(Item={**post}) 52 | 53 | return {"statusCode": 204, "headers": {"Access-Control-Allow-Origin": "*"}} 54 | -------------------------------------------------------------------------------- /lambda_forge/live/live_sns.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import boto3 4 | import click 5 | 6 | 7 | class LiveSNS: 8 | def __init__(self, region, account, printer): 9 | self.sns = boto3.client("sns", region_name=region) 10 | self.printer = printer 11 | self.region = region 12 | self.account = account 13 | self.lambda_client = boto3.client("lambda", region_name=region) 14 | 15 | def create_or_get_topic(self, topic_name): 16 | existent_topics = self.sns.list_topics() 17 | topics = existent_topics["Topics"] 18 | for topic in topics: 19 | if ( 20 | topic["TopicArn"] 21 | == f"arn:aws:sns:{self.region}:{self.account}:{topic_name}" 22 | ): 23 | return topic["TopicArn"] 24 | 25 | return self.sns.create_topic(Name=topic_name)["TopicArn"] 26 | 27 | def create_trigger(self, function_arn, stub_name, topic_arn): 28 | self.lambda_client.add_permission( 29 | FunctionName=stub_name, 30 | StatementId=str(uuid.uuid4()), 31 | Action="lambda:InvokeFunction", 32 | Principal="sns.amazonaws.com", 33 | SourceArn=topic_arn, 34 | ) 35 | 36 | self.sns.subscribe(TopicArn=topic_arn, Protocol="lambda", Endpoint=function_arn) 37 | return topic_arn 38 | 39 | def publish(self): 40 | self.printer.show_banner("SNS") 41 | message = click.prompt(click.style("Message", fg=(37, 171, 190)), type=str) 42 | self.sns.publish(TopicArn=self.topic_arn, Message=message) 43 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/make_guess/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from dataclasses import dataclass 4 | 5 | import boto3 6 | 7 | 8 | @dataclass 9 | class Path: 10 | game_id: str 11 | 12 | 13 | @dataclass 14 | class Input: 15 | guess: int 16 | 17 | 18 | @dataclass 19 | class Output: 20 | answer: str 21 | 22 | 23 | # Main handler function for the Lambda to process incoming requests 24 | def lambda_handler(event, context): 25 | # Initialize a DynamoDB resource using boto3 and get the table name from environment variables 26 | dynamodb = boto3.resource("dynamodb") 27 | NUMBERS_TABLE_NAME = os.environ.get("NUMBERS_TABLE_NAME", "Dev-Guess-The-Number") 28 | numbers_table = dynamodb.Table(NUMBERS_TABLE_NAME) 29 | 30 | # Extract the game_id from path parameters in the event object 31 | game_id = event["pathParameters"]["game_id"] 32 | # Extract the guess number from query string parameters in the event object 33 | guess = event["queryStringParameters"]["guess"] 34 | 35 | # Retrieve the item from DynamoDB based on the game_id 36 | response = numbers_table.get_item(Key={"PK": game_id}) 37 | # Extract the stored random number from the response 38 | random_number = int(response["Item"]["number"]) 39 | 40 | # Compare the guess to the random number and prepare the answer 41 | if int(guess) == random_number: 42 | answer = "correct" 43 | elif int(guess) < random_number: 44 | answer = "higher" 45 | else: 46 | answer = "lower" 47 | 48 | return {"statusCode": 200, "body": json.dumps({"answer": answer})} 49 | -------------------------------------------------------------------------------- /docs/examples/functions/images/qrcode/main.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import os 4 | from dataclasses import dataclass 5 | from io import BytesIO 6 | 7 | import boto3 8 | import qrcode 9 | 10 | 11 | @dataclass 12 | class Input: 13 | url: str 14 | email: str 15 | 16 | 17 | @dataclass 18 | class Output: 19 | pass 20 | 21 | 22 | def lambda_handler(event, context): 23 | 24 | # Parse the input event to get the URL of the image and the S3 bucket name 25 | body = json.loads(event["body"]) 26 | url = body.get("url") 27 | 28 | # Retrieve the S3 bucket name from environment variables 29 | bucket_name = os.environ.get( 30 | "BUCKET_NAME", "live-lambda-forge-examples-images-bucket" 31 | ) 32 | 33 | # Generate QR code from the image 34 | qr = qrcode.QRCode() 35 | qr.add_data(url) 36 | qr.make() 37 | 38 | # Create an image from the QR code 39 | qr_image = qr.make_image() 40 | 41 | # Convert the QR code image to bytes 42 | qr_byte_arr = BytesIO() 43 | qr_image.save(qr_byte_arr) 44 | qr_byte_arr = qr_byte_arr.getvalue() 45 | 46 | # Create the file name with a hash based on the input URL 47 | file_name = f"{hashlib.md5(url.encode()).hexdigest()}.jpg" 48 | 49 | # Initialize the S3 client 50 | s3_client = boto3.client("s3") 51 | 52 | # Upload the QR code image to S3 53 | s3_client.put_object( 54 | Bucket=bucket_name, 55 | Key=file_name, 56 | Body=qr_byte_arr, 57 | ContentType="image/png", 58 | Metadata={"url": url, "email": body.get("email")}, 59 | ) 60 | 61 | return {"statusCode": 200} 62 | -------------------------------------------------------------------------------- /docs/examples/infra/stacks/dev_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines as pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Dev", resources="dev") 14 | class DevStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "dev" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # post 39 | swagger = steps.swagger() 40 | redoc = steps.redoc() 41 | 42 | pipeline.add_stage(DeployStage(self, context), post=[swagger, redoc]) 43 | -------------------------------------------------------------------------------- /lambda_forge/stacks/default/dev_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines as pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Dev", resources="dev") 14 | class DevStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "dev" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # post 39 | swagger = steps.swagger() 40 | redoc = steps.redoc() 41 | 42 | pipeline.add_stage(DeployStage(self, context), post=[swagger, redoc]) 43 | -------------------------------------------------------------------------------- /docs/docs/examples/introduction.md: -------------------------------------------------------------------------------- 1 | In this guide, we'll take you on a journey through the development process with Lambda Forge, illustrating the progression of projects through a hands-on, step-by-step approach within a unified codebase. Our methodology employs an incremental build strategy, where each new feature enhances the foundation laid by preceding projects, ensuring a cohesive and scalable architecture without duplicating efforts. 2 | 3 | To keep our focus sharp on AWS resources and Lambda Forge architecture, we'll skip over the detailed discussion of unit and integration tests here. Our objective is to provide a streamlined and informative learning path, striking a balance between technical detail and approachability to keep you engaged without feeling overwhelmed. 4 | 5 | To enhance usability and the overall user experience, we've implemented a custom domain, `https://api.lambda-forge.com`, making our URLs succinct and memorable across various deployment stages: 6 | 7 | - **Dev** - `https://api.lambda-forge.com/dev` 8 | - **Staging** - `https://api.lambda-forge.com/staging` 9 | - **Prod** - `https://api.lambda-forge.com` 10 | 11 | 12 | With that in mind, let's kick off the project by running the command below: 13 | 14 | ``` 15 | forge project --name lambda-forge-examples --repo-owner "$GITHUB-OWNER" --repo-name "$GITHUB-REPO" --bucket "$S3-BUCKET" --account "$AWS-ACCOUNT" 16 | ``` 17 | 18 | **API Docs**: [https://api.lambda-forge.com/docs](https://examples.lambda-forge.com/docs). 19 | 20 | **Source code**: [https://github.com/GuiPimenta-Dev/lambda-forge/tree/master/docs/examples](https://github.com/GuiPimenta-Dev/lambda-forge/tree/master/docs/examples) 21 | -------------------------------------------------------------------------------- /ecr/generate_wiki.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | if __name__ == "__main__": 5 | file_path = sys.argv[1] 6 | title = sys.argv[2] 7 | favicon = sys.argv[3] 8 | 9 | # Read the Markdown content from the file 10 | with open(file_path, "r", encoding="utf-8") as file: 11 | content = file.read() 12 | 13 | content_escaped = json.dumps(content) 14 | 15 | html_template = f""" 16 | 17 | 18 | 19 | {title} 20 | 21 | 22 | 37 | 38 | 39 |
40 | 51 | 52 | 53 | """ 54 | 55 | # Save the HTML content to a file with UTF-8 encoding 56 | with open(f"{title}.html", "w") as file: 57 | file.write(html_template) 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="lambda_forge", 5 | version="2.2.2", 6 | packages=find_packages(), 7 | license="MIT", 8 | install_requires=[ 9 | "attrs==22.1.0", 10 | "aws-cdk-lib>=2.0.0,<3.0.0", 11 | "constructs>=10.0.0,<11.0.0", 12 | "boto3==1.26.59", 13 | "click==8.1.3", 14 | "pytest<7.0.0", 15 | "pytest-sugar==1.0.0", 16 | "coverage==7.2.3", 17 | "python-dotenv==1.0.1", 18 | "b-aws-websocket-api==2.0.0", 19 | "requests==2.31.0", 20 | "AWSIoTPythonSDK==1.5.4", 21 | "awslambdaric==2.0.11", 22 | "pyfiglet==1.0.2", 23 | "inquirerpy==0.3.4", 24 | "tabulate==0.9.0", 25 | "diagrams==0.23.4", 26 | "textual==0.75.1", 27 | "textual-serve==1.0.3" 28 | ], 29 | include_package_data=True, 30 | package_data={ 31 | "lambda_forge": [ 32 | "builders/*", 33 | "builders/**/*", 34 | "scaffold/*", 35 | "scaffold/.gitignore", 36 | "scaffold/.coveragerc", 37 | "scaffold/**/**", 38 | "scaffold/**/**/*", 39 | "api_gateway/*", 40 | "live/*", 41 | "live/**", 42 | "stacks/*", 43 | "stacks/**/*", 44 | ], 45 | }, 46 | author="Guilherme Alves Pimenta", 47 | author_email="guialvespimenta27@gmail.com", 48 | description="Lambda Forge is a framework to help you create lambda functions following a pre-defined structure.", 49 | entry_points={"console_scripts": ["forge=lambda_forge.forge:forge"]}, 50 | ) 51 | -------------------------------------------------------------------------------- /lambda_forge/live/live_event.py: -------------------------------------------------------------------------------- 1 | import json 2 | import uuid 3 | 4 | import boto3 5 | import click 6 | 7 | 8 | class LiveEventBridge: 9 | def __init__(self, region, printer): 10 | self.event_client = boto3.client("events", region_name=region) 11 | self.lambda_client = boto3.client("lambda", region_name=region) 12 | self.printer = printer 13 | self.region = region 14 | 15 | def create_bus(self, bus_name): 16 | buses = self.event_client.list_event_buses(NamePrefix=bus_name) 17 | bus_exists = any(bus["Name"] == bus_name for bus in buses["EventBuses"]) 18 | 19 | if not bus_exists: 20 | self.event_client.create_event_bus(Name=bus_name) 21 | 22 | def subscribe(self, function_arn, account_id, bus_name): 23 | rule_name = "Live-Rule" 24 | self.lambda_client.add_permission( 25 | FunctionName=function_arn, 26 | StatementId=str(uuid.uuid4()), 27 | Action="lambda:InvokeFunction", 28 | Principal="events.amazonaws.com", 29 | SourceArn=f"arn:aws:events:{self.region}:{account_id}:rule/{bus_name}/{rule_name}", 30 | ) 31 | 32 | # Create or update rule to target Lambda 33 | self.event_client.put_rule( 34 | Name=rule_name, 35 | EventBusName=bus_name, 36 | EventPattern=json.dumps({"source": ["event.bridge"]}), 37 | State="ENABLED", 38 | ) 39 | self.event_client.put_targets( 40 | Rule=rule_name, 41 | EventBusName=bus_name, 42 | Targets=[{"Id": "target1", "Arn": function_arn}], 43 | ) 44 | 45 | return bus_name 46 | -------------------------------------------------------------------------------- /docs/demo/functions/external/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | 4 | import requests 5 | 6 | 7 | @dataclass 8 | class Input: 9 | pass 10 | 11 | 12 | @dataclass 13 | class Name: 14 | title: str 15 | first: str 16 | last: str 17 | 18 | 19 | @dataclass 20 | class Street: 21 | number: int 22 | name: str 23 | 24 | 25 | @dataclass 26 | class Coordinates: 27 | latitude: str 28 | longitude: str 29 | 30 | 31 | @dataclass 32 | class Timezone: 33 | offset: str 34 | description: str 35 | 36 | 37 | @dataclass 38 | class Location: 39 | street: Street 40 | city: str 41 | state: str 42 | country: str 43 | postcode: int 44 | coordinates: Coordinates 45 | timezone: Timezone 46 | 47 | 48 | @dataclass 49 | class Login: 50 | uuid: str 51 | username: str 52 | password: str 53 | salt: str 54 | md5: str 55 | sha1: str 56 | sha256: str 57 | 58 | 59 | @dataclass 60 | class Output: 61 | gender: str 62 | name: Name 63 | location: Location 64 | email: str 65 | login: Login 66 | phone: str 67 | 68 | 69 | def lambda_handler(event, context): 70 | 71 | result = requests.get("https://randomuser.me/api").json()["results"][0] 72 | 73 | data = { 74 | "gender": result["gender"], 75 | "name": result["name"], 76 | "location": result["location"], 77 | "email": result["email"], 78 | "login": result["login"], 79 | "phone": result["phone"], 80 | } 81 | 82 | return { 83 | "statusCode": 200, 84 | "body": json.dumps(data), 85 | "headers": {"Access-Control-Allow-Origin": "*"}, 86 | } 87 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.te.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}}); -------------------------------------------------------------------------------- /docs/examples/functions/auth/signup/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from dataclasses import dataclass 4 | 5 | import boto3 6 | 7 | 8 | @dataclass 9 | class Input: 10 | email: str 11 | password: int 12 | 13 | 14 | @dataclass 15 | class Output: 16 | pass 17 | 18 | 19 | def encrypt_with_kms(plaintext: str, kms_key_id: str) -> str: 20 | kms_client = boto3.client("kms") 21 | response = kms_client.encrypt(KeyId=kms_key_id, Plaintext=plaintext.encode()) 22 | return response["CiphertextBlob"] 23 | 24 | 25 | def lambda_handler(event, context): 26 | # Retrieve the DynamoDB table name and KMS key ID from environment variables. 27 | AUTH_TABLE_NAME = os.environ.get("AUTH_TABLE_NAME") 28 | KMS_KEY_ID = os.environ.get("KMS_KEY_ID") 29 | 30 | # Initialize a DynamoDB resource. 31 | dynamodb = boto3.resource("dynamodb") 32 | 33 | # Reference the DynamoDB table. 34 | auth_table = dynamodb.Table(AUTH_TABLE_NAME) 35 | 36 | # Parse the request body to get user data. 37 | body = json.loads(event["body"]) 38 | 39 | # Verify if the user already exists. 40 | user = auth_table.get_item(Key={"PK": body["email"]}) 41 | if user.get("Item"): 42 | return { 43 | "statusCode": 400, 44 | "body": json.dumps({"message": "User already exists"}), 45 | } 46 | 47 | # Encrypt the password using KMS. 48 | encrypted_password = encrypt_with_kms(body["password"], KMS_KEY_ID) 49 | 50 | # Insert the new user into the DynamoDB table. 51 | auth_table.put_item(Item={"PK": body["email"], "password": encrypted_password}) 52 | 53 | # Return a successful response with the newly created user ID. 54 | return {"statusCode": 201} 55 | -------------------------------------------------------------------------------- /docs/examples/infra/services/layers.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_lambda as _lambda 2 | 3 | from lambda_forge.path import Path 4 | 5 | 6 | class Layers: 7 | def __init__(self, scope) -> None: 8 | 9 | self.qrcode_layer = _lambda.LayerVersion.from_layer_version_arn( 10 | scope, 11 | id="QrCodeLayer", 12 | layer_version_arn="arn:aws:lambda:us-east-2:211125768252:layer:QRCode:1", 13 | ) 14 | 15 | self.sm_utils_layer = _lambda.LayerVersion( 16 | scope, 17 | id="SmUtilsLayer", 18 | code=_lambda.Code.from_asset(Path.layer("layers/sm_utils")), 19 | compatible_runtimes=[_lambda.Runtime.PYTHON_3_9], 20 | description="", 21 | ) 22 | 23 | self.pyjwt_layer = _lambda.LayerVersion.from_layer_version_arn( 24 | scope, 25 | id="JWTLayer", 26 | layer_version_arn="arn:aws:lambda:us-east-2:770693421928:layer:Klayers-p39-PyJWT:3", 27 | ) 28 | 29 | self.requests_layer = _lambda.LayerVersion.from_layer_version_arn( 30 | scope, 31 | id="RequestsLayer", 32 | layer_version_arn="arn:aws:lambda:us-east-2:770693421928:layer:Klayers-p39-requests:19", 33 | ) 34 | 35 | self.bs4_layer = _lambda.LayerVersion.from_layer_version_arn( 36 | scope, 37 | id="BS4Layer", 38 | layer_version_arn="arn:aws:lambda:us-east-2:770693421928:layer:Klayers-p39-beautifulsoup4:7", 39 | ) 40 | 41 | self.iot = _lambda.LayerVersion.from_layer_version_arn( 42 | scope, 43 | id="IotLayer", 44 | layer_version_arn="arn:aws:lambda:us-east-2:211125768252:layer:awsiot:1", 45 | ) 46 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.ta.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Za-zA-Z0-90-9",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}}); -------------------------------------------------------------------------------- /docs/examples/functions/blog/comment_post/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import uuid 4 | from dataclasses import dataclass 5 | from datetime import datetime 6 | 7 | import boto3 8 | 9 | 10 | @dataclass 11 | class Path: 12 | post_id: str 13 | 14 | 15 | @dataclass 16 | class Input: 17 | comment: str 18 | 19 | 20 | @dataclass 21 | class Output: 22 | comment_id: str 23 | 24 | 25 | def lambda_handler(event, context): 26 | 27 | dynamodb = boto3.resource("dynamodb") 28 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 29 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 30 | 31 | comment_id = str(uuid.uuid4()) 32 | post_id = event.get("pathParameters", {}).get("post_id") 33 | 34 | email = event["requestContext"]["authorizer"]["email"] 35 | first_name = event["requestContext"]["authorizer"]["first_name"] 36 | last_name = event["requestContext"]["authorizer"]["last_name"] 37 | picture = event["requestContext"]["authorizer"]["picture"] 38 | 39 | body = json.loads(event["body"]) 40 | comment = body.get("comment") 41 | created_at = datetime.now().isoformat() 42 | post = posts_table.get_item(Key={"PK": post_id}).get("Item") 43 | comment = { 44 | "comment_id": comment_id, 45 | "comment": comment, 46 | "email": email, 47 | "first_name": first_name, 48 | "last_name": last_name, 49 | "picture": picture, 50 | "created_at": created_at, 51 | } 52 | post["comments"].append(comment) 53 | 54 | posts_table.put_item(Item={**post}) 55 | 56 | return { 57 | "statusCode": 201, 58 | "body": json.dumps({"comment_id": comment_id}), 59 | "headers": {"Access-Control-Allow-Origin": "*"}, 60 | } 61 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/update_post/main.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | from dataclasses import dataclass 5 | 6 | import boto3 7 | 8 | 9 | @dataclass 10 | class Path: 11 | post_id: str 12 | 13 | 14 | @dataclass 15 | class Input: 16 | pass 17 | 18 | 19 | @dataclass 20 | class Output: 21 | message: str 22 | 23 | 24 | def lambda_handler(event, context): 25 | 26 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 27 | dynamodb = boto3.resource("dynamodb") 28 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 29 | 30 | post_id = event.get("pathParameters", {}).get("post_id") 31 | 32 | post = posts_table.get_item(Key={"PK": post_id}).get("Item") 33 | 34 | # update a post feature 35 | body = base64.b64decode(event["body"]).decode("utf-8") 36 | 37 | # Parse the body to extract file content and file name 38 | boundary = body.split("\n")[0].strip() 39 | parts = body.split(boundary) 40 | 41 | title = None 42 | file_content = None 43 | 44 | for part in parts: 45 | if 'Content-Disposition: form-data; name="file_content"' in part: 46 | file_content = part.split("\r\n\r\n")[1].strip() 47 | elif 'Content-Disposition: form-data; name="title"' in part: 48 | title = part.split("\r\n\r\n")[1].strip() 49 | 50 | if not title or not file_content: 51 | raise ValueError("File name or content not found in the request") 52 | 53 | post["title"] = title 54 | post["content"] = file_content 55 | 56 | posts_table.put_item(Item={**post}) 57 | 58 | return { 59 | "statusCode": 201, 60 | "body": json.dumps({"post_id": post_id}), 61 | "headers": {"Access-Control-Allow-Origin": "*"}, 62 | } 63 | -------------------------------------------------------------------------------- /lambda_forge/live/main.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | import pickle 5 | import time 6 | 7 | from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient 8 | 9 | 10 | def lambda_handler(event, context): 11 | CLIENT_ID = os.environ.get("CLIENT_ID") 12 | ENDPOINT = os.environ.get("ENDPOINT") 13 | TIMEOUT = float(os.environ.get("TIMEOUT_SECONDS")) 14 | PORT = 443 15 | 16 | mqtt_client = AWSIoTMQTTClient(CLIENT_ID) 17 | mqtt_client.configureEndpoint(ENDPOINT, PORT) 18 | 19 | current_dir = os.path.dirname(os.path.abspath(__file__)) 20 | ca = current_dir + "/ca.pem" 21 | private_key = current_dir + "/private_key.pem" 22 | certificate = current_dir + "/certificate.pem" 23 | mqtt_client.configureCredentials(ca, private_key, certificate) 24 | mqtt_client.connect() 25 | 26 | global response 27 | response = None 28 | 29 | def callback(client, userdata, message): 30 | global response 31 | response = message.payload.decode() 32 | try: 33 | mqtt_client.disconnect() 34 | except Exception as e: 35 | print(f"Unexpected error during disconnect: {e}") 36 | 37 | mqtt_client.subscribe(f"{CLIENT_ID}/response", 0, callback) 38 | 39 | payload = {"event": event, "context": context} 40 | payload_bytes = pickle.dumps(payload) 41 | payload_base64 = base64.b64encode(payload_bytes).decode("utf-8") 42 | mqtt_client.publish(f"{CLIENT_ID}/request", payload_base64, 0) 43 | 44 | start_time = time.time() 45 | while response is None and (time.time() - start_time) < TIMEOUT: 46 | time.sleep(0.1) 47 | 48 | if response is None: 49 | return {"statusCode": 408, "body": "Request Timeout"} 50 | 51 | return json.loads(response) 52 | -------------------------------------------------------------------------------- /docs/examples/infra/stacks/staging_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines as pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Staging", resources="staging") 14 | class StagingStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "staging" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # post 39 | redoc = steps.redoc() 40 | swagger = steps.swagger() 41 | 42 | pipeline.add_stage( 43 | DeployStage(self, context), 44 | post=[ 45 | redoc, 46 | swagger, 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /lambda_forge/stacks/no_docs/prod_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Prod", resources="prod") 14 | class ProdStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "main" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # pre 39 | unit_tests = steps.unit_tests() 40 | integration_tests = steps.integration_tests() 41 | 42 | pipeline.add_stage( 43 | DeployStage(self, context), 44 | pre=[ 45 | unit_tests, 46 | integration_tests, 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /docs/examples/functions/guess_the_number/create_game/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import random 4 | import uuid 5 | from dataclasses import dataclass 6 | 7 | import boto3 8 | 9 | 10 | @dataclass 11 | class Input: 12 | min_number: int 13 | max_number: int 14 | 15 | 16 | @dataclass 17 | class Output: 18 | game_id: str 19 | 20 | 21 | def lambda_handler(event, context): 22 | # Initialize a DynamoDB resource using the boto3 library 23 | dynamodb = boto3.resource("dynamodb") 24 | # Retrieve the DynamoDB table name from environment variables 25 | NUMBERS_TABLE_NAME = os.environ.get("NUMBERS_TABLE_NAME", "Dev-Guess-The-Number") 26 | numbers_table = dynamodb.Table(NUMBERS_TABLE_NAME) 27 | 28 | body = json.loads(event["body"]) 29 | 30 | # Get the min and max number from the body 31 | min_number = body.get("min_number", 1) 32 | max_number = body.get("max_number", 100) 33 | 34 | # Validate that the initial number is less than the end number 35 | if min_number >= max_number: 36 | return { 37 | "statusCode": 400, 38 | "body": json.dumps({"message": "min_number must be less than max_number"}), 39 | } 40 | 41 | # Generate a unique game ID using uuid 42 | game_id = str(uuid.uuid4()) 43 | # Generate a random number between the initial and end numbers 44 | random_number = random.randint(min_number, max_number) 45 | 46 | # Store the game ID and the random number in DynamoDB 47 | numbers_table.put_item( 48 | Item={ 49 | "PK": game_id, 50 | "number": random_number, 51 | } 52 | ) 53 | 54 | return { 55 | "statusCode": 200, 56 | "body": json.dumps({"game_id": game_id}), 57 | "headers": {"Access-Control-Allow-Origin": "*"}, 58 | } 59 | -------------------------------------------------------------------------------- /docs/examples/infra/stacks/prod_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Prod", resources="prod") 14 | class ProdStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "main" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # post 39 | diagram = steps.diagram() 40 | redoc = steps.redoc() 41 | swagger = steps.swagger() 42 | 43 | pipeline.add_stage( 44 | DeployStage(self, context), 45 | post=[ 46 | diagram, 47 | redoc, 48 | swagger, 49 | ], 50 | ) 51 | -------------------------------------------------------------------------------- /docs/site/assets/javascripts/lunr/min/lunr.zh.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}}); -------------------------------------------------------------------------------- /lambda_forge/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | 5 | 6 | class Path: 7 | _instance = None 8 | _temp_dir = None 9 | 10 | def __new__(cls, *args, **kwargs): 11 | if cls._instance is None: 12 | cls._instance = super(Path, cls).__new__(cls) 13 | cls._temp_dir = tempfile.mkdtemp() 14 | return cls._instance 15 | 16 | @staticmethod 17 | def handler(directory): 18 | return ( 19 | f"src.{directory}.main.lambda_handler" 20 | if directory 21 | else "src.main.lambda_handler" 22 | ) 23 | 24 | @staticmethod 25 | def function(src): 26 | if Path._temp_dir is None: 27 | Path() 28 | 29 | # Adjust the path to be relative to the temporary directory 30 | relative_path = src.split("functions/")[1] if "functions/" in src else src 31 | destination_path = os.path.join(Path._temp_dir, relative_path) 32 | 33 | # Ensure the destination directory exists 34 | os.makedirs(os.path.dirname(destination_path), exist_ok=True) 35 | 36 | # Copy the source directory to the destination 37 | shutil.copytree(src, f"{destination_path}/src", dirs_exist_ok=True) 38 | 39 | return destination_path 40 | 41 | @staticmethod 42 | def layer(path): 43 | if Path._temp_dir is None: 44 | Path() 45 | 46 | # Adjust the path to be relative to the temporary directory 47 | destination_path = os.path.join(Path._temp_dir, path) 48 | 49 | # Ensure the destination directory exists 50 | os.makedirs(os.path.dirname(destination_path), exist_ok=True) 51 | 52 | # Copy the source directory to the destination 53 | shutil.copytree(path, f"{destination_path}/python", dirs_exist_ok=True) 54 | 55 | return destination_path 56 | -------------------------------------------------------------------------------- /lambda_forge/live/live_sqs.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import click 3 | 4 | from lambda_forge.live.live_iam import LiveIAM 5 | 6 | 7 | class LiveSQS: 8 | def __init__(self, region, printer): 9 | self.sqs = boto3.client("sqs", region_name=region) 10 | self.iam = LiveIAM(region) 11 | self.iam_client = boto3.client("iam", region_name=region) 12 | self.printer = printer 13 | self.lambda_client = boto3.client("lambda", region_name=region) 14 | 15 | def create_queue(self, name): 16 | queue_url = self.sqs.create_queue(QueueName=name)["QueueUrl"] 17 | response = self.sqs.get_queue_attributes( 18 | QueueUrl=queue_url, AttributeNames=["QueueArn"] 19 | ) 20 | return queue_url, response["Attributes"]["QueueArn"] 21 | 22 | def subscribe(self, function_arn, queue_url, queue_arn): 23 | 24 | policy = { 25 | "Version": "2012-10-17", 26 | "Statement": [{"Effect": "Allow", "Action": "sqs:*", "Resource": "*"}], 27 | } 28 | 29 | try: 30 | self.iam = self.iam.attach_policy_to_lambda( 31 | policy, function_arn, "Live-SQS-Policy" 32 | ) 33 | except: 34 | pass 35 | 36 | self.sqs.set_queue_attributes( 37 | QueueUrl=queue_url, Attributes={"VisibilityTimeout": "900"} 38 | ) 39 | 40 | self.lambda_client.create_event_source_mapping( 41 | EventSourceArn=queue_arn, FunctionName=function_arn, Enabled=True 42 | ) 43 | 44 | return queue_url 45 | 46 | def publish(self): 47 | message = click.prompt(click.style("Message", fg=(37, 171, 190)), type=str) 48 | self.sqs.send_message( 49 | QueueUrl=self.queue_url, 50 | MessageBody=message, 51 | Subject="Message from Lambda Forge", 52 | ) 53 | -------------------------------------------------------------------------------- /lambda_forge/scaffold/infra/services/api_gateway.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_apigateway as apigateway 2 | 3 | from lambda_forge.api_gateway import REST 4 | from lambda_forge.trackers import trigger 5 | 6 | 7 | class APIGateway: 8 | def __init__(self, scope, context): 9 | 10 | api = apigateway.RestApi( 11 | scope, 12 | id=context.create_id("REST"), 13 | deploy_options={"stage_name": context.stage.lower()}, 14 | endpoint_types=[apigateway.EndpointType.REGIONAL], 15 | binary_media_types=["multipart/form-data"], 16 | default_cors_preflight_options={ 17 | "allow_origins": ["*"], 18 | "allow_methods": apigateway.Cors.ALL_METHODS, 19 | "allow_credentials": True, 20 | }, 21 | ) 22 | 23 | self.rest = REST(scope=scope, api=api, context=context) 24 | 25 | @trigger( 26 | service="api_gateway", 27 | trigger="path", 28 | function="function", 29 | extra=["method", "public"], 30 | ) 31 | def create_endpoint(self, method, path, function, public=False, authorizer=None): 32 | self.rest.create_endpoint( 33 | method=method, 34 | path=path, 35 | function=function, 36 | public=public, 37 | authorizer=authorizer, 38 | ) 39 | 40 | def create_authorizer(self, authorizer, name, default=False): 41 | self.rest.create_authorizer(authorizer=authorizer, name=name, default=default) 42 | 43 | def create_docs( 44 | self, endpoint, artifact, authorizer=None, public=False, stages=None 45 | ): 46 | self.rest.create_docs( 47 | endpoint=endpoint, 48 | artifact=artifact, 49 | authorizer=authorizer, 50 | public=public, 51 | stages=stages, 52 | ) 53 | -------------------------------------------------------------------------------- /docs/examples/infra/services/api_gateway.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_apigateway as apigateway 2 | 3 | from lambda_forge.api_gateway import REST 4 | from lambda_forge.trackers import trigger 5 | 6 | 7 | class APIGateway: 8 | def __init__(self, scope, context): 9 | 10 | self.api = apigateway.RestApi( 11 | scope, 12 | id=context.create_id("APIGateway"), 13 | deploy_options={"stage_name": context.stage.lower()}, 14 | endpoint_types=[apigateway.EndpointType.REGIONAL], 15 | binary_media_types=["multipart/form-data"], 16 | default_cors_preflight_options={ 17 | "allow_origins": ["*"], 18 | "allow_methods": apigateway.Cors.ALL_METHODS, 19 | "allow_credentials": True, 20 | }, 21 | ) 22 | 23 | self.rest = REST(scope=scope, api=self.api, context=context) 24 | 25 | @trigger( 26 | service="api_gateway", 27 | trigger="path", 28 | function="function", 29 | extra=["method", "public"], 30 | ) 31 | def create_endpoint(self, method, path, function, public=False, authorizer=None): 32 | self.rest.create_endpoint( 33 | method=method, 34 | path=path, 35 | function=function, 36 | public=public, 37 | authorizer=authorizer, 38 | ) 39 | 40 | def create_authorizer(self, authorizer, name, default=False): 41 | self.rest.create_authorizer(authorizer=authorizer, name=name, default=default) 42 | 43 | def create_docs( 44 | self, endpoint, artifact, authorizer=None, public=False, stages=None 45 | ): 46 | self.rest.create_docs( 47 | endpoint=endpoint, 48 | artifact=artifact, 49 | authorizer=authorizer, 50 | public=public, 51 | stages=stages, 52 | ) 53 | -------------------------------------------------------------------------------- /docs/demo/infra/services/api_gateway.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_apigateway as apigateway 2 | 3 | from lambda_forge.api_gateway import REST 4 | from lambda_forge.trackers import trigger 5 | 6 | 7 | class APIGateway: 8 | def __init__(self, scope, context): 9 | 10 | api = apigateway.RestApi( 11 | scope, 12 | id=context.create_id("APIGateway"), 13 | deploy_options={"stage_name": context.stage.lower()}, 14 | endpoint_types=[apigateway.EndpointType.REGIONAL], 15 | binary_media_types=["multipart/form-data"], 16 | endpoint_export_name="BASE-URL", 17 | default_cors_preflight_options={ 18 | "allow_origins": ["*"], 19 | "allow_methods": apigateway.Cors.ALL_METHODS, 20 | "allow_credentials": True, 21 | }, 22 | ) 23 | 24 | self.rest = REST(scope=scope, api=api, context=context) 25 | 26 | @trigger( 27 | service="api_gateway", 28 | trigger="path", 29 | function="function", 30 | extra=["method", "public"], 31 | ) 32 | def create_endpoint(self, method, path, function, public=False, authorizer=None): 33 | self.rest.create_endpoint( 34 | method=method, 35 | path=path, 36 | function=function, 37 | public=public, 38 | authorizer=authorizer, 39 | ) 40 | 41 | def create_authorizer(self, function, name, default=False): 42 | self.rest.create_authorizer(authorizer=function, name=name, default=default) 43 | 44 | def create_docs( 45 | self, endpoint, artifact, authorizer=None, public=False, stages=None 46 | ): 47 | self.rest.create_docs( 48 | endpoint=endpoint, 49 | artifact=artifact, 50 | authorizer=authorizer, 51 | public=public, 52 | stages=stages, 53 | ) 54 | -------------------------------------------------------------------------------- /docs/demo/infra/stacks/dev_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines as pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Dev", resources="dev") 14 | class DevStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "dev" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # post 39 | swagger = steps.swagger() 40 | redoc = steps.redoc() 41 | wikis = [ 42 | { 43 | "title": "Wiki", 44 | "file_path": "docs/wikis/wiki.md", 45 | "favicon": "https://docs.lambda-forge.com/images/favicon.png", 46 | } 47 | ] 48 | wikis = steps.wikis(wikis) 49 | 50 | pipeline.add_stage(DeployStage(self, context), post=[swagger, redoc, wikis]) 51 | -------------------------------------------------------------------------------- /lambda_forge/builders/file_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from importlib import resources 4 | from pathlib import Path 5 | from typing import List 6 | 7 | 8 | class FileService: 9 | 10 | root_dir = os.getcwd() 11 | 12 | def join(self, *args) -> str: 13 | return os.path.join(*args) 14 | 15 | def file_exists(self, path: str) -> bool: 16 | return os.path.exists(path) 17 | 18 | def make_dir(self, path: str) -> None: 19 | os.makedirs(path, exist_ok=True) 20 | 21 | def make_file(self, path: str, name, content: str = "") -> None: 22 | with open(os.path.join(path, name), "w") as f: 23 | f.write(content) 24 | 25 | def write_lines(self, path: str, lines: List) -> None: 26 | with open(path, "w") as f: 27 | for line in lines: 28 | f.write(line) 29 | 30 | def read_lines(self, path: str) -> List: 31 | with open(path, "r") as f: 32 | return f.readlines() 33 | 34 | def copy_file( 35 | self, package_name, resource_name: str, full_destination: str 36 | ) -> None: 37 | dst = Path(self.root_dir) / full_destination 38 | src = resources.files(package_name) / resource_name 39 | dst.parent.mkdir(parents=True, exist_ok=True) 40 | shutil.copy2(src, dst) 41 | 42 | def copy_folders( 43 | self, package_name: str, resource_name: str, destination: str 44 | ) -> None: 45 | dst = Path(self.root_dir) / destination 46 | src = resources.files(package_name) / resource_name 47 | dst.mkdir(parents=True, exist_ok=True) 48 | for src_path in src.glob("**/*"): 49 | if src_path.is_file(): 50 | dst_path = dst / src_path.relative_to(src) 51 | dst_path.parent.mkdir( 52 | parents=True, exist_ok=True 53 | ) # Ensure parent directory exists 54 | shutil.copy2(src_path, dst_path) 55 | -------------------------------------------------------------------------------- /docs/examples/functions/blog/create_post/main.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | import uuid 5 | from dataclasses import dataclass 6 | from datetime import datetime 7 | 8 | import boto3 9 | 10 | 11 | @dataclass 12 | class Input: 13 | title: str 14 | file_content: str 15 | 16 | 17 | @dataclass 18 | class Output: 19 | message: str 20 | 21 | 22 | def lambda_handler(event, context): 23 | 24 | dynamodb = boto3.resource("dynamodb") 25 | 26 | POSTS_TABLE_NAME = os.environ.get("POSTS_TABLE_NAME", "Dev-Blog-Posts") 27 | 28 | posts_table = dynamodb.Table(POSTS_TABLE_NAME) 29 | 30 | post_id = str(uuid.uuid4()) 31 | email = event["requestContext"]["authorizer"]["email"] 32 | 33 | body = base64.b64decode(event["body"]).decode("utf-8") 34 | 35 | # Parse the body to extract file content and file name 36 | boundary = body.split("\n")[0].strip() 37 | parts = body.split(boundary) 38 | 39 | title = None 40 | file_content = None 41 | 42 | for part in parts: 43 | if 'Content-Disposition: form-data; name="file_content"' in part: 44 | file_content = part.split("\r\n\r\n")[1].strip() 45 | elif 'Content-Disposition: form-data; name="title"' in part: 46 | title = part.split("\r\n\r\n")[1].strip() 47 | 48 | if not title or not file_content: 49 | raise ValueError("File name or content not found in the request") 50 | 51 | created_at = datetime.now().isoformat() 52 | 53 | posts_table.put_item( 54 | Item={ 55 | "PK": post_id, 56 | "email": email, 57 | "title": title, 58 | "content": file_content, 59 | "comments": [], 60 | "likes": [], 61 | "created_at": created_at, 62 | } 63 | ) 64 | 65 | return { 66 | "statusCode": 201, 67 | "body": json.dumps({"post_id": post_id}), 68 | "headers": {"Access-Control-Allow-Origin": "*"}, 69 | } 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Lambda Forge 2 | 3 | Thank you for your interest in contributing to Lambda Forge! We welcome contributions of all kinds, including bug fixes, new features, documentation improvements, and discussions. 4 | 5 | ## Getting Started 6 | 7 | 1. **Fork the Repository**: Click the "Fork" button at the top of the repository. 8 | 2. **Clone Your Fork**: 9 | ```sh 10 | git clone https://github.com/GuiPimenta-Dev/lambda-forge.git 11 | cd lambda-forge 12 | ``` 13 | 3. **Create a Branch**: 14 | ```sh 15 | git checkout -b feature-or-bugfix-name 16 | ``` 17 | 4. **Install Dependencies**: 18 | ```sh 19 | pip install -r requirements.txt 20 | ``` 21 | 5. **Run Tests**: 22 | ```sh 23 | pytest 24 | ``` 25 | 26 | ## Contribution Guidelines 27 | 28 | ### Reporting Issues 29 | - Before opening a new issue, check the [existing issues](https://github.com/lambda-forge/lambda-forge/issues). 30 | - Provide clear steps to reproduce the issue. 31 | - If applicable, include error messages and logs. 32 | 33 | ### Adding a New Feature 34 | - Open an issue first to discuss the idea. 35 | - Follow the project’s existing architecture and conventions. 36 | - Write unit tests for new functionality. 37 | - Update the documentation if needed. 38 | 39 | ### Submitting a Pull Request 40 | 1. **Ensure your code is formatted**: 41 | ```sh 42 | black . 43 | isort . 44 | ``` 45 | 2. **Run all tests before submitting**: 46 | ```sh 47 | pytest 48 | ``` 49 | 3. **Push your branch**: 50 | ```sh 51 | git push origin feature-or-bugfix-name 52 | ``` 53 | 4. **Open a Pull Request**: 54 | - Go to the repository and create a new Pull Request (PR). 55 | - Describe the changes and reference any related issues. 56 | - Ensure all checks pass before requesting a review. 57 | 58 | ## Code Style 59 | - Follow [PEP 8](https://peps.python.org/pep-0008/) for Python code. 60 | - Use `black` and `isort` for consistent formatting. 61 | 62 | -------------------------------------------------------------------------------- /lambda_forge/live/tui/ui/widgets/server.py: -------------------------------------------------------------------------------- 1 | from rich.text import Text 2 | from rich.console import RenderableType 3 | from textual.binding import Binding 4 | from textual.widget import Widget 5 | from rich.table import Table 6 | 7 | from lambda_forge.live.tui.api.forge import ForgeAPI 8 | 9 | forge = ForgeAPI() 10 | 11 | 12 | class ServerTable(Widget, can_focus=True): 13 | DEFAULT_CSS = """ 14 | ServerTable { 15 | height: auto; 16 | } 17 | """ 18 | COMPONENT_CLASSES = { 19 | "table-header", 20 | "table-border", 21 | "row-name", 22 | "row-service", 23 | "row-type", 24 | "row-trigger", 25 | } 26 | 27 | COLUMNS = ["Name", "Service", "Type", "Trigger"] 28 | RATIOS = [None, None, None, 1] 29 | BINDINGS = [ 30 | Binding("r", "refresh_servers", "Refresh Servers"), 31 | ] 32 | 33 | def render(self) -> RenderableType: 34 | table = Table(expand=True) 35 | table.show_lines = True 36 | table.border_style = self.get_component_rich_style("table-border") 37 | 38 | for column, ratio in zip(self.COLUMNS, self.RATIOS): 39 | table.add_column( 40 | column, 41 | ratio=ratio, 42 | header_style=self.get_component_rich_style("table-header"), 43 | ) 44 | 45 | for row in forge.get_servers(): 46 | row = [ 47 | Text(row[0] or "", style=self.get_component_rich_style("row-name")), 48 | Text(row[1] or "", style=self.get_component_rich_style("row-service")), 49 | Text(row[2] or "", style=self.get_component_rich_style("row-type")), 50 | Text(row[3] or "", style=self.get_component_rich_style("row-trigger")), 51 | ] 52 | table.add_row(*row) 53 | 54 | return table 55 | 56 | def on_show(self): 57 | self.focus() 58 | 59 | def action_refresh_servers(self): 60 | self.refresh() 61 | -------------------------------------------------------------------------------- /docs/examples/functions/images/mailer/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | 32 |
33 | 38 |

Your Image Is Ready!

39 |

Hello,

40 |

41 | We're excited to let you know that your image has been processed and is 42 | now attached to this email. 43 |

44 | 45 |

Please check the attachment to view it.

46 | 47 |

48 | Made with ❤️ by 49 | Lambda Forge 56 |

57 |
58 | 59 | -------------------------------------------------------------------------------- /docs/examples/infra/services/dynamodb.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import aws_dynamodb as dynamodb 2 | from aws_cdk import aws_lambda as lambda_ 3 | from aws_cdk import aws_lambda_event_sources as event_source 4 | 5 | from lambda_forge.trackers import invoke, trigger 6 | 7 | 8 | class DynamoDB: 9 | def __init__(self, scope, context) -> None: 10 | 11 | self.numbers_table = dynamodb.Table.from_table_arn( 12 | scope, 13 | "NumbersTable", 14 | context.resources["arns"]["numbers_table"], 15 | ) 16 | 17 | self.urls_table = dynamodb.Table.from_table_arn( 18 | scope, 19 | "UrlsTable", 20 | context.resources["arns"]["urls_table"], 21 | ) 22 | 23 | self.auth_table = dynamodb.Table.from_table_arn( 24 | scope, 25 | "AuthTable", 26 | context.resources["arns"]["auth_table"], 27 | ) 28 | 29 | self.books_table = dynamodb.Table.from_table_arn( 30 | scope, 31 | "BooksTable", 32 | "arn:aws:dynamodb:us-east-2:211125768252:table/Books", 33 | ) 34 | 35 | self.posts_table = dynamodb.Table.from_table_arn( 36 | scope, 37 | "PostsTable", 38 | "arn:aws:dynamodb:us-east-2:211125768252:table/Dev-Blog-Posts", 39 | ) 40 | 41 | @trigger(service="dynamodb", trigger="table", function="function") 42 | def create_trigger(self, table: str, function: lambda_.Function) -> None: 43 | table_instance = getattr(self, table) 44 | dynamo_event_stream = event_source.DynamoEventSource( 45 | table_instance, starting_position=lambda_.StartingPosition.TRIM_HORIZON 46 | ) 47 | function.add_event_source(dynamo_event_stream) 48 | 49 | @invoke(service="dynamodb", resource="table", function="function") 50 | def grant_write(self, table: str, function: lambda_.Function) -> None: 51 | table_instance = getattr(self, table) 52 | table_instance.grant_write_data(function) 53 | -------------------------------------------------------------------------------- /deploy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import shutil 4 | import subprocess 5 | 6 | 7 | def read_version(): 8 | """Reads the version from setup.py.""" 9 | with open("setup.py", "r") as file: 10 | content = file.read() 11 | version_match = re.search(r"version=['\"]([^'\"]+)['\"]", content) 12 | if version_match: 13 | return version_match[1] 14 | return None 15 | 16 | 17 | def increment_version(version): 18 | """Increments the patch number in the version.""" 19 | major, minor, patch = map(int, version.split(".")) 20 | return f"{major}.{minor}.{patch + 1}" 21 | 22 | 23 | def update_setup_py(new_version): 24 | """Updates the setup.py file with the new version.""" 25 | with open("setup.py", "r") as file: 26 | content = file.read() 27 | content = re.sub( 28 | r"(version=['\"])([^'\"]+)(['\"])", rf"\g<1>{new_version}\3", content 29 | ) 30 | with open("setup.py", "w") as file: 31 | file.write(content) 32 | 33 | 34 | def build_and_upload(): 35 | # Check if the dist directory exists and remove it 36 | dist_path = "dist" 37 | if os.path.exists(dist_path) and os.path.isdir(dist_path): 38 | shutil.rmtree(dist_path) 39 | print(f"Removed existing {dist_path} directory.") 40 | 41 | """Builds the package and uploads it to TestPyPI.""" 42 | subprocess.run(["python", "setup.py", "sdist", "bdist_wheel"], check=True) 43 | subprocess.run(["twine", "upload", "--repository", "pypi", "dist/*"], check=True) 44 | 45 | 46 | def main(): 47 | current_version = read_version() 48 | if current_version is None: 49 | raise Exception("Could not read the current version from setup.py.") 50 | new_version = increment_version(current_version) 51 | print(f"Updating version: {current_version} -> {new_version}") 52 | update_setup_py(new_version) 53 | print("Building and uploading the package to TestPyPI...") 54 | build_and_upload() 55 | print("Done.") 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /docs/demo/infra/stacks/prod_stack.py: -------------------------------------------------------------------------------- 1 | import aws_cdk as cdk 2 | from aws_cdk import aws_codebuild as codebuild 3 | from aws_cdk import pipelines 4 | from aws_cdk.pipelines import CodePipelineSource 5 | from constructs import Construct 6 | from infra.stages.deploy import DeployStage 7 | 8 | from lambda_forge.constants import ECR 9 | from lambda_forge.context import context 10 | from lambda_forge.steps import CodeBuildSteps 11 | 12 | 13 | @context(stage="Prod", resources="prod") 14 | class ProdStack(cdk.Stack): 15 | def __init__(self, scope: Construct, context, **kwargs) -> None: 16 | super().__init__(scope, context.create_id("Stack"), **kwargs) 17 | 18 | source = CodePipelineSource.git_hub( 19 | f"{context.repo['owner']}/{context.repo['name']}", "main" 20 | ) 21 | 22 | pipeline = pipelines.CodePipeline( 23 | self, 24 | "Pipeline", 25 | pipeline_name=context.create_id("Pipeline"), 26 | synth=pipelines.ShellStep("Synth", input=source, commands=["cdk synth"]), 27 | code_build_defaults=pipelines.CodeBuildOptions( 28 | build_environment=codebuild.BuildEnvironment( 29 | build_image=codebuild.LinuxBuildImage.from_docker_registry( 30 | ECR.LATEST 31 | ), 32 | ) 33 | ), 34 | ) 35 | 36 | steps = CodeBuildSteps(self, context, source=source) 37 | 38 | # pre 39 | unit_tests = steps.unit_tests() 40 | integration_tests = steps.integration_tests() 41 | 42 | # post 43 | diagram = steps.diagram() 44 | redoc = steps.redoc() 45 | swagger = steps.swagger() 46 | 47 | pipeline.add_stage( 48 | DeployStage(self, context), 49 | pre=[ 50 | unit_tests, 51 | integration_tests, 52 | ], 53 | post=[ 54 | diagram, 55 | redoc, 56 | swagger, 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /lambda_forge/logs/tui/api/_test_data.py: -------------------------------------------------------------------------------- 1 | log_groups = [ 2 | ("/aws/lambda/Live-Telegram-AskQuestion", "Live Telegram AskQuestion"), 3 | ("/aws/lambda/Live-Whatsapp-AskQuestion", "Live WhatsApp AskQuestion"), 4 | ("/aws/lambda/Live-Instagram-AskQuestion", "Live Instagram AskQuestion"), 5 | ("/aws/lambda/Live-Telegram-AskQuestion-2", "Live Telegram AskQuestion 2"), 6 | ("/aws/lambda/Live-Whatsapp-AskQuestion-2", "Live WhatsApp AskQuestion 2"), 7 | ] 8 | 9 | cloudwatch_logs = [ 10 | { 11 | "timestamp": "1723345218900", 12 | "message": "INIT_START Runtime Version: python:3.9.v55 Runtime Version ARN: arn:aws:lambda:us-east-2::runtime:be9e7121d3264b1e86158b38dbbb656c23dff979eb481793ee37b9e2b79fda22", 13 | }, 14 | { 15 | "timestamp": "1723345219013", 16 | "message": "START RequestId: eb5ea470-b2c6-4739-9c47-cc770c36fea6 Version: $LATEST", 17 | }, 18 | { 19 | "timestamp": "1723345220026", 20 | "message": "END RequestId: eb5ea470-b2c6-4739-9c47-cc770c36fea6", 21 | }, 22 | { 23 | "timestamp": "1723345220026", 24 | "message": "REPORT RequestId: eb5ea470-b2c6-4739-9c47-cc770c36fea6 Duration: 1013.31 ms Billed Duration: 1014 ms Memory Size: 128 MB Max Memory Used: 46 MB Init Duration: 111.94 ms", 25 | }, 26 | { 27 | "timestamp": "1723345237339", 28 | "message": "START RequestId: a719c94f-0532-4394-b47d-28595a7b3105 Version: $LATEST", 29 | }, 30 | { 31 | "timestamp": "1723345238004", 32 | "message": "END RequestId: a719c94f-0532-4394-b47d-28595a7b3105", 33 | }, 34 | { 35 | "timestamp": "1723345238004", 36 | "message": "REPORT RequestId: a719c94f-0532-4394-b47d-28595a7b3105 Duration: 665.52 ms Billed Duration: 666 ms Memory Size: 128 MB Max Memory Used: 47 MB", 37 | }, 38 | { 39 | "timestamp": "1723345616747", 40 | "message": "[ERROR] 2024-08-11T03:06:56.747Z a719c94f-0532-4394-b47d-28595a7b3105 Disconnect timed out", 41 | }, 42 | ] * 2 43 | --------------------------------------------------------------------------------