├── 2023 ├── .gitattributes ├── AWS-Serverless-Architecture.png ├── README.md ├── api_gateway.tf ├── dynamodb.tf ├── lambda_get.tf ├── lambda_post.tf ├── main.tf ├── outputs.tf ├── serverlesstf-demo-may-2024.mov └── versions.tf ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── hashitalks2021 ├── AWS-Serverless-Architecture.png ├── README.md ├── acm.tf ├── api_gateway.tf ├── api_gateway_put_events_to_eventbridge_role.tf ├── appsync.tf ├── deploy.tf ├── dynamodb-data-importer │ └── main.tf ├── dynamodb.tf ├── efs.tf ├── lambda_get.tf ├── lambda_post.tf ├── main.tf ├── opa │ ├── enforce_aws_resource.rego │ └── scalr-policy.hcl ├── outputs.tf ├── s3_bucket.tf ├── schema.graphql ├── step_function.asl.yaml ├── step_function.tf └── tmp │ ├── eventbridge_rule.tf │ ├── sample-webhook.json │ └── test-webhook.sh ├── nicconf ├── README.md ├── api_gateway.tf ├── dynamodb.tf ├── infracost_usage.yml ├── lambda_get.tf ├── lambda_post.tf ├── main.tf └── outputs.tf ├── open@amazon ├── AWS-Serverless-Architecture.png ├── README.md ├── alt_options │ └── options_for_screenshots.tf ├── api_gateway.tf ├── dynamodb.tf ├── lambda_get.tf ├── lambda_post.tf ├── main.tf ├── open@amazon-terraform-aws-modules-june-2021.pdf ├── outputs.tf └── s3_bucket.tf ├── rag ├── README.md ├── agent_lambda.tf ├── main.tf ├── outputs.tf ├── requirements.txt ├── src │ ├── bedrock_agent │ │ ├── agent_bedrock_schema.json │ │ └── handler.py │ └── kb_rag.py └── versions.tf └── src ├── go-function └── main.go └── python-function ├── get.py ├── index.py ├── post.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | *.tfstate* 4 | terraform.tfvars 5 | .terraform.lock.hcl 6 | 7 | builds/ 8 | 9 | .venv/ 10 | __pycache__/ 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/antonbabenko/pre-commit-terraform 3 | rev: v1.99.0 4 | hooks: 5 | - id: terraform_fmt 6 | - id: terraform_validate 7 | - id: terraform_docs 8 | args: 9 | - --args=--lockfile=false 10 | - id: terraform_tflint 11 | # - id: terraform_checkov 12 | # - id: terraform_tfsec 13 | - id: infracost_breakdown 14 | verbose: true 15 | args: 16 | - --args=--path=nicconf 17 | - --args=--usage-file=nicconf/infracost_usage.yml 18 | - --args=--log-level=warn 19 | - --hook-config='.totalMonthlyCost|tonumber < 10' 20 | -------------------------------------------------------------------------------- /2023/.gitattributes: -------------------------------------------------------------------------------- 1 | *.mov filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /2023/AWS-Serverless-Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/e5b25c431503b22cd216f47bf523300d4738d734/2023/AWS-Serverless-Architecture.png -------------------------------------------------------------------------------- /2023/README.md: -------------------------------------------------------------------------------- 1 | # Doing serverless on AWS with Terraform for real 2 | 3 | This repository contains code for my serverless talks. 4 | 5 | The architecture created by this code ([source](https://ordina-jworks.github.io/cloud/2019/01/14/Infrastructure-as-code-with-terraform-and-aws-serverless.html)): 6 | 7 | ![AWS-Serverless-Architecture](https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/master/hashitalks2021/AWS-Serverless-Architecture.png) 8 | 9 | 10 | ## Getting started 11 | 12 | Run `terraform init` and `terraform apply` to get everything created. 13 | 14 | Call API Gateway endpoint using GET or POST methods, for eg: 15 | 16 | ``` 17 | # Get all items 18 | $ http GET $(terraform output -raw api_endpoint) 19 | 20 | # Add a new item 21 | $ http POST $(terraform output -raw api_endpoint) 22 | ``` 23 | 24 | ## Using LocalStack 25 | 26 | ``` 27 | # Setting LocalStack API Key is required to emulate Amazon API Gateway with LocalStack 28 | $ export LOCALSTACK_API_KEY= 29 | $ export GATEWAY_LISTEN="0.0.0.0:4566" 30 | 31 | # Start LocalStack 32 | $ localstack start -d 33 | 34 | # Get all items 35 | $ http GET $(terraform output -raw api_endpoint) 36 | 37 | # Add a new item 38 | $ http POST $(terraform output -raw api_endpoint) 39 | ``` 40 | 41 | 42 | 43 | ## Requirements 44 | 45 | | Name | Version | 46 | |------|---------| 47 | | [terraform](#requirement\_terraform) | >= 1.0 | 48 | | [aws](#requirement\_aws) | >= 4.63 | 49 | | [random](#requirement\_random) | >= 3.0 | 50 | 51 | ## Providers 52 | 53 | | Name | Version | 54 | |------|---------| 55 | | [random](#provider\_random) | >= 3.0 | 56 | 57 | ## Modules 58 | 59 | | Name | Source | Version | 60 | |------|--------|---------| 61 | | [api\_gateway](#module\_api\_gateway) | terraform-aws-modules/apigateway-v2/aws | ~> 5.0 | 62 | | [dynamodb\_table](#module\_dynamodb\_table) | terraform-aws-modules/dynamodb-table/aws | ~> 4.0 | 63 | | [lambda\_get](#module\_lambda\_get) | terraform-aws-modules/lambda/aws | ~> 7.0 | 64 | | [lambda\_post](#module\_lambda\_post) | terraform-aws-modules/lambda/aws | ~> 7.0 | 65 | 66 | ## Resources 67 | 68 | | Name | Type | 69 | |------|------| 70 | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | 71 | 72 | ## Inputs 73 | 74 | No inputs. 75 | 76 | ## Outputs 77 | 78 | | Name | Description | 79 | |------|-------------| 80 | | [api\_endpoint](#output\_api\_endpoint) | FQDN of an API endpoint | 81 | | [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | 82 | | [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | 83 | | [lambda\_get\_function\_name](#output\_lambda\_get\_function\_name) | The name of the Lambda Function - GET | 84 | | [lambda\_post\_function\_name](#output\_lambda\_post\_function\_name) | The name of the Lambda Function - POST | 85 | 86 | -------------------------------------------------------------------------------- /2023/api_gateway.tf: -------------------------------------------------------------------------------- 1 | module "api_gateway" { 2 | source = "terraform-aws-modules/apigateway-v2/aws" 3 | version = "~> 5.0" 4 | 5 | name = "${random_pet.this.id}-http" 6 | description = "My awesome HTTP API Gateway" 7 | protocol_type = "HTTP" 8 | 9 | create_domain_name = false 10 | 11 | routes = { 12 | "GET /" = { 13 | integration = { 14 | uri = module.lambda_get.lambda_function_arn 15 | payload_format_version = "2.0" 16 | } 17 | } 18 | "POST /" = { 19 | integration = { 20 | uri = module.lambda_post.lambda_function_arn 21 | payload_format_version = "2.0" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /2023/dynamodb.tf: -------------------------------------------------------------------------------- 1 | module "dynamodb_table" { 2 | source = "terraform-aws-modules/dynamodb-table/aws" 3 | version = "~> 4.0" 4 | 5 | name = random_pet.this.id 6 | hash_key = "id" 7 | range_key = "name" 8 | 9 | attributes = [ 10 | { 11 | name = "id" 12 | type = "S" 13 | }, 14 | { 15 | name = "name" 16 | type = "S" 17 | }, 18 | ] 19 | } 20 | 21 | # resource "aws_dynamodb_table_item" "this" { 22 | # table_name = module.dynamodb_table.dynamodb_table_id 23 | # hash_key = "id" 24 | # range_key = "name" 25 | 26 | # item = jsonencode({ 27 | # "id" = {"S": "1"} 28 | # "name" = {"S": "test"} 29 | # }) 30 | # } 31 | -------------------------------------------------------------------------------- /2023/lambda_get.tf: -------------------------------------------------------------------------------- 1 | module "lambda_get" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 7.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-get" 6 | description = "My awesome Python lambda function" 7 | handler = "get.lambda_handler" 8 | runtime = "python3.13" 9 | publish = true 10 | 11 | source_path = "../src/python-function/get.py" 12 | 13 | attach_tracing_policy = true 14 | attach_policy_statements = true 15 | 16 | environment_variables = { 17 | DYNAMODB_TABLE_NAME = module.dynamodb_table.dynamodb_table_id 18 | } 19 | 20 | policy_statements = { 21 | dynamodb_read = { 22 | effect = "Allow", 23 | actions = ["dynamodb:Scan"], 24 | resources = [module.dynamodb_table.dynamodb_table_arn] 25 | } 26 | } 27 | 28 | allowed_triggers = { 29 | AllowExecutionFromAPIGateway = { 30 | service = "apigateway" 31 | source_arn = "${module.api_gateway.api_execution_arn}/*/GET/*" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /2023/lambda_post.tf: -------------------------------------------------------------------------------- 1 | module "lambda_post" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 7.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-post" 6 | description = "My awesome Python lambda function" 7 | handler = "post.lambda_handler" 8 | runtime = "python3.13" 9 | publish = true 10 | 11 | source_path = [ 12 | { 13 | path = "${path.module}/../src/python-function" 14 | pip_requirements = true 15 | patterns = [ 16 | "!.*\\.dist-info/.*", 17 | "!\\.venv/.*", 18 | ] 19 | } 20 | ] 21 | 22 | attach_tracing_policy = true 23 | attach_policy_statements = true 24 | 25 | environment_variables = { 26 | DYNAMODB_TABLE_NAME = module.dynamodb_table.dynamodb_table_id 27 | } 28 | 29 | policy_statements = { 30 | dynamodb_write = { 31 | effect = "Allow", 32 | actions = ["dynamodb:PutItem"], 33 | resources = [module.dynamodb_table.dynamodb_table_arn] 34 | } 35 | } 36 | 37 | allowed_triggers = { 38 | AllowExecutionFromAPIGateway = { 39 | service = "apigateway" 40 | source_arn = "${module.api_gateway.api_execution_arn}/*/POST/*" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /2023/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | } 4 | 5 | # locals { 6 | # domain_name = "terraform-aws-modules.modules.tf" # trimsuffix(data.aws_route53_zone.this.name, ".") 7 | # subdomain = "serverless-playground" 8 | # } 9 | 10 | resource "random_pet" "this" { 11 | length = 2 12 | } 13 | -------------------------------------------------------------------------------- /2023/outputs.tf: -------------------------------------------------------------------------------- 1 | ######################## 2 | # Lambda Function - GET 3 | ######################## 4 | output "lambda_get_function_name" { 5 | description = "The name of the Lambda Function - GET" 6 | value = module.lambda_get.lambda_function_name 7 | } 8 | 9 | ######################## 10 | # Lambda Function - POST 11 | ######################## 12 | output "lambda_post_function_name" { 13 | description = "The name of the Lambda Function - POST" 14 | value = module.lambda_post.lambda_function_name 15 | } 16 | 17 | ################# 18 | # DynamoDB Table 19 | ################# 20 | output "dynamodb_table_arn" { 21 | description = "ARN of the DynamoDB table" 22 | value = module.dynamodb_table.dynamodb_table_arn 23 | } 24 | 25 | output "dynamodb_table_id" { 26 | description = "ID of the DynamoDB table" 27 | value = module.dynamodb_table.dynamodb_table_id 28 | } 29 | 30 | ######################## 31 | # API Gateway 32 | ######################## 33 | output "api_endpoint" { 34 | description = "FQDN of an API endpoint" 35 | value = module.api_gateway.api_endpoint 36 | } 37 | -------------------------------------------------------------------------------- /2023/serverlesstf-demo-may-2024.mov: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:959d6616e11c9348a72ef715f3724e2b873b5898a0e1db83415b2b899c1691c0 3 | size 494757709 4 | -------------------------------------------------------------------------------- /2023/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 4.63" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = ">= 3.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Anton Babenko (Betajob AS) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## serverless.tf playground to show it in action 2 | 3 | This repository is a playground for [serverless.tf](https://github.com/antonbabenko/serverless.tf) approach, which aims to simplify all operations when working with the serverless in Terraform. 4 | 5 | 6 | ## Flow 7 | 8 | 1. Source code for Lambda Functions is in `src` folder. 9 | 1. All infrastructure configuration and deployment configurations is in `hashitalks2021` folder. 10 | 1. When code changes, and developer wants to "deploy it to prod", run `cd terraform && terraform init && terraform apply`. This will build deployment package, update resources, and deploy all to _prod_ using AWS CodeDeploy. 11 | 12 | ## Useful commands 13 | 14 | ``` 15 | $ http https://vdlsaentfnbvtaes7aduvzrwdm.appsync-api.eu-west-1.amazonaws.com/graphql x-api-key:da2-1enspdfpdvfvlk4efjqto4oks4 query='{ listPets { id name } }' 16 | ``` 17 | 18 | ## To-do 19 | 20 | 1. Use Terragrunt for orchestration (deploy one by one or all together with respective dependencies) 21 | 1. Group resources (eg, by layer (lambdas, for eg), environment, or region) 22 | 23 | 24 | ## Authors 25 | 26 | This repository managed by [Anton Babenko](https://github.com/antonbabenko). Check out [serverless.tf](https://serverless.tf) to learn more about doing serverless with Terraform. 27 | 28 | Please reach out to [Betajob](https://www.betajob.com/) if you are looking for commercial support for your Terraform, AWS, or serverless project. 29 | 30 | 31 | ## License 32 | 33 | Apache 2 Licensed. See LICENSE for full details. 34 | -------------------------------------------------------------------------------- /hashitalks2021/AWS-Serverless-Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/e5b25c431503b22cd216f47bf523300d4738d734/hashitalks2021/AWS-Serverless-Architecture.png -------------------------------------------------------------------------------- /hashitalks2021/README.md: -------------------------------------------------------------------------------- 1 | # Doing serverless on AWS with Terraform for real 2 | 3 | This repository contains code for my talk at HashiTalks 2021 plus several more additions described during live-streams: 4 | 5 | - March 12th, 2021 - [Webhooks/serverless processing with Terraform on AWS (part 1)](https://www.youtube.com/watch?v=9EjTrmhI6Ug) 6 | - March 19th, 2021 - [Webhooks/serverless processing with Terraform on AWS (part 2)](https://www.youtube.com/watch?v=ug5JJrhfzHs) 7 | 8 | The architecture created by this code ([source](https://ordina-jworks.github.io/cloud/2019/01/14/Infrastructure-as-code-with-terraform-and-aws-serverless.html)): 9 | 10 | ![AWS-Serverless-Architecture](https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/master/hashitalks2021/AWS-Serverless-Architecture.png) 11 | 12 | 13 | ## Getting started 14 | 15 | Run `terraform init` and `terraform apply` to get everything created. 16 | 17 | Call API Gateway endpoint using GET or POST methods, for eg: 18 | 19 | ``` 20 | $ http GET $(terraform output -raw apigatewayv2_api_api_endpoint) 21 | ``` 22 | 23 | 24 | ## How to update and deploy changes? 25 | 26 | 1. Update source code of the Lambda Functions inside `../src/python-function` 27 | 2. Run `terraform apply` to rebuild Lambda Function package (if necessary) and update the dependencies. 28 | 29 | PS: There is a way to do complex deployments of AWS Lambda functions using AWS CodeDeploy service, see [tmp-deploy directory for code](https://github.com/antonbabenko/serverless.tf-playground/tree/master/hashitalks2021/tmp-deploy). 30 | 31 | 32 | 33 | 34 | ## Requirements 35 | 36 | No requirements. 37 | 38 | ## Providers 39 | 40 | | Name | Version | 41 | |------|---------| 42 | | [aws](#provider\_aws) | n/a | 43 | | [random](#provider\_random) | n/a | 44 | 45 | ## Modules 46 | 47 | | Name | Source | Version | 48 | |------|--------|---------| 49 | | [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 3.0 | 50 | | [api\_gateway](#module\_api\_gateway) | terraform-aws-modules/apigateway-v2/aws | ~> 1.0 | 51 | | [apigateway\_put\_events\_to\_eventbridge\_policy](#module\_apigateway\_put\_events\_to\_eventbridge\_policy) | terraform-aws-modules/iam/aws//modules/iam-policy | ~> 4.0 | 52 | | [apigateway\_put\_events\_to\_eventbridge\_role](#module\_apigateway\_put\_events\_to\_eventbridge\_role) | terraform-aws-modules/iam/aws//modules/iam-assumable-role | ~> 4.0 | 53 | | [appsync](#module\_appsync) | terraform-aws-modules/appsync/aws | ~> 1 | 54 | | [dynamodb\_table](#module\_dynamodb\_table) | terraform-aws-modules/dynamodb-table/aws | ~> 1.0 | 55 | | [lambda\_get](#module\_lambda\_get) | terraform-aws-modules/lambda/aws | ~> 2.0 | 56 | | [lambda\_post](#module\_lambda\_post) | terraform-aws-modules/lambda/aws | ~> 2.0 | 57 | | [records](#module\_records) | terraform-aws-modules/route53/aws//modules/records | ~> 2.0 | 58 | | [step\_function](#module\_step\_function) | terraform-aws-modules/step-functions/aws | ~> 2.0 | 59 | 60 | ## Resources 61 | 62 | | Name | Type | 63 | |------|------| 64 | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | 65 | | [aws_iam_policy_document.apigateway_put_events_to_eventbridge_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 66 | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | 67 | 68 | ## Inputs 69 | 70 | No inputs. 71 | 72 | ## Outputs 73 | 74 | | Name | Description | 75 | |------|-------------| 76 | | [api\_endpoint](#output\_api\_endpoint) | FQDN of an API endpoint | 77 | | [api\_fqdn](#output\_api\_fqdn) | List of Route53 records | 78 | | [apigateway\_put\_events\_to\_eventbridge\_role\_arn](#output\_apigateway\_put\_events\_to\_eventbridge\_role\_arn) | n/a | 79 | | [apigatewayv2\_api\_api\_endpoint](#output\_apigatewayv2\_api\_api\_endpoint) | The URI of the API | 80 | | [apigatewayv2\_api\_arn](#output\_apigatewayv2\_api\_arn) | The ARN of the API | 81 | | [apigatewayv2\_api\_execution\_arn](#output\_apigatewayv2\_api\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 82 | | [apigatewayv2\_api\_id](#output\_apigatewayv2\_api\_id) | The API identifier | 83 | | [apigatewayv2\_domain\_name\_configuration](#output\_apigatewayv2\_domain\_name\_configuration) | The domain name configuration | 84 | | [apigatewayv2\_domain\_name\_id](#output\_apigatewayv2\_domain\_name\_id) | The domain name identifier | 85 | | [apigatewayv2\_hosted\_zone\_id](#output\_apigatewayv2\_hosted\_zone\_id) | The Amazon Route 53 Hosted Zone ID of the endpoint | 86 | | [apigatewayv2\_target\_domain\_name](#output\_apigatewayv2\_target\_domain\_name) | The target domain name | 87 | | [default\_apigatewayv2\_stage\_execution\_arn](#output\_default\_apigatewayv2\_stage\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 88 | | [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | 89 | | [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | 90 | | [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | 91 | | [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | 92 | | [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | 93 | | [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | 94 | | [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | 95 | 96 | -------------------------------------------------------------------------------- /hashitalks2021/acm.tf: -------------------------------------------------------------------------------- 1 | ###### 2 | # ACM 3 | ###### 4 | 5 | data "aws_route53_zone" "this" { 6 | name = local.domain_name 7 | } 8 | 9 | module "acm" { 10 | source = "terraform-aws-modules/acm/aws" 11 | version = "~> 3.0" 12 | 13 | domain_name = local.domain_name 14 | zone_id = data.aws_route53_zone.this.id 15 | subject_alternative_names = ["${local.subdomain}.${local.domain_name}"] 16 | } 17 | 18 | ########## 19 | # Route53 20 | ########## 21 | 22 | module "records" { 23 | source = "terraform-aws-modules/route53/aws//modules/records" 24 | version = "~> 2.0" 25 | 26 | zone_id = data.aws_route53_zone.this.zone_id 27 | 28 | records = [ 29 | { 30 | name = local.subdomain 31 | type = "A" 32 | alias = { 33 | name = module.api_gateway.apigatewayv2_domain_name_configuration[0].target_domain_name 34 | zone_id = module.api_gateway.apigatewayv2_domain_name_configuration[0].hosted_zone_id 35 | } 36 | }, 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /hashitalks2021/api_gateway.tf: -------------------------------------------------------------------------------- 1 | module "api_gateway" { 2 | source = "terraform-aws-modules/apigateway-v2/aws" 3 | version = "~> 1.0" 4 | 5 | name = "${random_pet.this.id}-http" 6 | description = "My awesome HTTP API Gateway" 7 | protocol_type = "HTTP" 8 | 9 | domain_name = "${local.subdomain}.${local.domain_name}" 10 | domain_name_certificate_arn = module.acm.acm_certificate_arn 11 | 12 | integrations = { 13 | "GET /" = { 14 | lambda_arn = module.lambda_get.lambda_function_arn 15 | payload_format_version = "2.0" 16 | } 17 | 18 | "POST /" = { 19 | lambda_arn = module.lambda_post.lambda_function_arn 20 | payload_format_version = "2.0" 21 | } 22 | 23 | "POST /start-step-function" = { 24 | integration_type = "AWS_PROXY" 25 | integration_subtype = "StepFunctions-StartExecution" 26 | 27 | # @todo: Using the same IAM role as Step Function is not the best solution, but I do it anyway :) 28 | credentials_arn = module.step_function.role_arn 29 | 30 | # Note: jsonencode is used to pass argument as a string 31 | request_parameters = jsonencode({ 32 | StateMachineArn = module.step_function.state_machine_arn 33 | }) 34 | 35 | payload_format_version = "1.0" 36 | timeout_milliseconds = 12000 37 | } 38 | 39 | "POST /webhook" = { 40 | integration_type = "AWS_PROXY" 41 | integration_subtype = "EventBridge-PutEvents" 42 | credentials_arn = module.apigateway_put_events_to_eventbridge_role.iam_role_arn 43 | 44 | request_parameters = jsonencode({ 45 | DetailType = "Webhook from external service", 46 | Detail = "$request.body", 47 | Source = "webhook" 48 | }) 49 | 50 | payload_format_version = "1.0" 51 | } 52 | 53 | "$default" = { 54 | lambda_arn = module.lambda_get.lambda_function_arn 55 | } 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /hashitalks2021/api_gateway_put_events_to_eventbridge_role.tf: -------------------------------------------------------------------------------- 1 | module "apigateway_put_events_to_eventbridge_role" { 2 | source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" 3 | version = "~> 4.0" 4 | 5 | create_role = true 6 | 7 | role_name = "apigateway-put-events-to-eventbridge" 8 | role_requires_mfa = false 9 | 10 | trusted_role_services = [ 11 | "apigateway.amazonaws.com" 12 | ] 13 | 14 | custom_role_policy_arns = [ 15 | module.apigateway_put_events_to_eventbridge_policy.arn 16 | ] 17 | } 18 | 19 | module "apigateway_put_events_to_eventbridge_policy" { 20 | source = "terraform-aws-modules/iam/aws//modules/iam-policy" 21 | version = "~> 4.0" 22 | 23 | name = "apigateway-put-events-to-eventbridge" 24 | path = "/" 25 | description = "Allow PutEvents to EventBridge" 26 | 27 | policy = data.aws_iam_policy_document.apigateway_put_events_to_eventbridge_policy.json 28 | } 29 | 30 | data "aws_iam_policy_document" "apigateway_put_events_to_eventbridge_policy" { 31 | statement { 32 | sid = "AllowPutEvents" 33 | actions = ["events:PutEvents"] 34 | resources = ["*"] 35 | } 36 | } 37 | 38 | output "apigateway_put_events_to_eventbridge_role_arn" { 39 | value = module.apigateway_put_events_to_eventbridge_role.iam_role_arn 40 | } -------------------------------------------------------------------------------- /hashitalks2021/appsync.tf: -------------------------------------------------------------------------------- 1 | module "appsync" { 2 | source = "terraform-aws-modules/appsync/aws" 3 | version = "~> 1" 4 | 5 | name = random_pet.this.id 6 | 7 | schema = file("schema.graphql") 8 | 9 | api_keys = { 10 | default = null 11 | } 12 | 13 | datasources = { 14 | # lambda1 = { 15 | # type = "AWS_LAMBDA" 16 | # 17 | # Note: dynamic references (module.aws_lambda_function1.lambda_function_arn) do not work unless you create this resource in advance 18 | # function_arn = "arn:aws:lambda:eu-west-1:835367859851:function:index_1" 19 | # } 20 | 21 | dynamodb1 = { 22 | type = "AMAZON_DYNAMODB" 23 | 24 | # Note: dynamic references (module.dynamodb_table1.dynamodb_table_id) do not work unless you create this resource in advance 25 | table_name = random_pet.this.id 26 | region = "eu-west-1" 27 | } 28 | } 29 | 30 | resolvers = { 31 | # "Mutation.putPet" = { 32 | # data_source = "lambda1" 33 | # direct_lambda = true 34 | # } 35 | 36 | "Query.listPets" = { 37 | data_source = "dynamodb1" 38 | request_template = < API Gateway => EventBridge => https://smee.io/123 4 | 5 | http --json --print=HhBb POST https://serverless-playground.terraform-aws-modules.modules.tf/webhook @sample-webhook.json 6 | -------------------------------------------------------------------------------- /nicconf/README.md: -------------------------------------------------------------------------------- 1 | # Code for my talk at NICConf 2022 2 | 3 | ![AWS-Serverless-Architecture](https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/master/hashitalks2021/AWS-Serverless-Architecture.png) 4 | 5 | 6 | ## Getting started 7 | 8 | Run `terraform init` and `terraform apply` to get everything created. 9 | 10 | Call API Gateway endpoint using GET or POST methods, for eg: 11 | 12 | ``` 13 | $ http GET $(terraform output -raw apigatewayv2_api_api_endpoint) 14 | ``` 15 | 16 | 17 | 18 | ## Requirements 19 | 20 | No requirements. 21 | 22 | ## Providers 23 | 24 | | Name | Version | 25 | |------|---------| 26 | | [random](#provider\_random) | n/a | 27 | 28 | ## Modules 29 | 30 | | Name | Source | Version | 31 | |------|--------|---------| 32 | | [api\_gateway](#module\_api\_gateway) | terraform-aws-modules/apigateway-v2/aws | ~> 1.0 | 33 | | [dynamodb\_table](#module\_dynamodb\_table) | terraform-aws-modules/dynamodb-table/aws | ~> 1.0 | 34 | | [lambda\_get](#module\_lambda\_get) | terraform-aws-modules/lambda/aws | ~> 3.0 | 35 | | [lambda\_post](#module\_lambda\_post) | terraform-aws-modules/lambda/aws | ~> 3.0 | 36 | 37 | ## Resources 38 | 39 | | Name | Type | 40 | |------|------| 41 | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | 42 | 43 | ## Inputs 44 | 45 | No inputs. 46 | 47 | ## Outputs 48 | 49 | | Name | Description | 50 | |------|-------------| 51 | | [apigatewayv2\_api\_api\_endpoint](#output\_apigatewayv2\_api\_api\_endpoint) | The URI of the API | 52 | | [apigatewayv2\_api\_arn](#output\_apigatewayv2\_api\_arn) | The ARN of the API | 53 | | [apigatewayv2\_api\_execution\_arn](#output\_apigatewayv2\_api\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 54 | | [apigatewayv2\_api\_id](#output\_apigatewayv2\_api\_id) | The API identifier | 55 | | [apigatewayv2\_domain\_name\_configuration](#output\_apigatewayv2\_domain\_name\_configuration) | The domain name configuration | 56 | | [apigatewayv2\_domain\_name\_id](#output\_apigatewayv2\_domain\_name\_id) | The domain name identifier | 57 | | [apigatewayv2\_hosted\_zone\_id](#output\_apigatewayv2\_hosted\_zone\_id) | The Amazon Route 53 Hosted Zone ID of the endpoint | 58 | | [apigatewayv2\_target\_domain\_name](#output\_apigatewayv2\_target\_domain\_name) | The target domain name | 59 | | [command](#output\_command) | CLI command to call API Gateway | 60 | | [default\_apigatewayv2\_stage\_execution\_arn](#output\_default\_apigatewayv2\_stage\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 61 | | [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | 62 | | [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | 63 | | [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | 64 | | [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | 65 | | [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | 66 | | [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | 67 | | [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | 68 | 69 | -------------------------------------------------------------------------------- /nicconf/api_gateway.tf: -------------------------------------------------------------------------------- 1 | module "api_gateway" { 2 | source = "terraform-aws-modules/apigateway-v2/aws" 3 | version = "~> 1.0" 4 | 5 | name = "${random_pet.this.id}-http" 6 | description = "My awesome HTTP API Gateway" 7 | protocol_type = "HTTP" 8 | 9 | create_api_domain_name = false 10 | 11 | integrations = { 12 | "GET /" = { 13 | lambda_arn = module.lambda_get.lambda_function_arn 14 | payload_format_version = "2.0" 15 | } 16 | 17 | "POST /" = { 18 | lambda_arn = module.lambda_post.lambda_function_arn 19 | payload_format_version = "2.0" 20 | } 21 | 22 | "$default" = { 23 | lambda_arn = module.lambda_get.lambda_function_arn 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nicconf/dynamodb.tf: -------------------------------------------------------------------------------- 1 | module "dynamodb_table" { 2 | source = "terraform-aws-modules/dynamodb-table/aws" 3 | version = "~> 1.0" 4 | 5 | name = random_pet.this.id 6 | hash_key = "id" 7 | range_key = "name" 8 | 9 | attributes = [ 10 | { 11 | name = "id" 12 | type = "S" 13 | }, 14 | { 15 | name = "name" 16 | type = "S" 17 | }, 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /nicconf/infracost_usage.yml: -------------------------------------------------------------------------------- 1 | # You can use this file to define resource usage estimates for Infracost to use when calculating 2 | # the cost of usage-based resource, such as AWS S3 or Lambda. 3 | # `infracost breakdown --usage-file infracost-usage.yml [other flags]` 4 | # See https://infracost.io/usage-file/ for docs 5 | version: 0.1 6 | resource_usage: 7 | ## 8 | ## The following usage values are all commented-out, you can uncomment resources and customize as needed. 9 | ## 10 | # module.api_gateway.aws_apigatewayv2_api.this[0]: 11 | # monthly_requests: 0 # Monthly requests to the HTTP API Gateway. 12 | # request_size_kb: 0 # Average request size sent to the HTTP API Gateway in KB. Requests are metered in 512KB increments, maximum size is 10MB. 13 | # monthly_messages: 0 # Monthly number of messages sent to the Websocket API Gateway. 14 | # message_size_kb: 0 # Average size of the messages sent to the Websocket API Gateway in KB. Messages are metered in 32 KB increments, maximum size is 128KB. 15 | # monthly_connection_mins: 0 # Monthly total connection minutes to Websockets. 16 | # module.dynamodb_table.aws_dynamodb_table.this[0]: 17 | # monthly_write_request_units: 0 # Monthly write request units in (used for on-demand DynamoDB). 18 | # monthly_read_request_units: 0 # Monthly read request units in (used for on-demand DynamoDB). 19 | # storage_gb: 0 # Total storage for tables in GB. 20 | # pitr_backup_storage_gb: 0 # Total storage for Point-In-Time Recovery (PITR) backups in GB. 21 | # on_demand_backup_storage_gb: 0 # Total storage for on-demand backups in GB. 22 | # monthly_data_restored_gb: 0 # Monthly size of restored data in GB. 23 | # monthly_streams_read_request_units: 0 # Monthly streams read request units. 24 | # module.lambda_get.aws_cloudwatch_log_group.lambda[0]: 25 | # storage_gb: 0.0 # Total data stored by CloudWatch logs in GB. 26 | # monthly_data_ingested_gb: 0.0 # Monthly data ingested by CloudWatch logs in GB. 27 | # monthly_data_scanned_gb: 0.0 # Monthly data scanned by CloudWatch logs insights in GB. 28 | module.lambda_get.aws_lambda_function.this[0]: 29 | monthly_requests: 0 # Monthly requests to the Lambda function. 30 | request_duration_ms: 0 # Average duration of each request in milliseconds. 31 | module.lambda_post.aws_cloudwatch_log_group.lambda[0]: 32 | storage_gb: 5.0 # Total data stored by CloudWatch logs in GB. 33 | monthly_data_ingested_gb: 0.0 # Monthly data ingested by CloudWatch logs in GB. 34 | monthly_data_scanned_gb: 0.0 # Monthly data scanned by CloudWatch logs insights in GB. 35 | module.lambda_post.aws_lambda_function.this[0]: 36 | monthly_requests: 1000000 # Monthly requests to the Lambda function. 37 | request_duration_ms: 3000 # Average duration of each request in milliseconds. 38 | -------------------------------------------------------------------------------- /nicconf/lambda_get.tf: -------------------------------------------------------------------------------- 1 | module "lambda_get" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 3.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-get" 6 | description = "My awesome Python lambda function" 7 | handler = "index.lambda_handler" 8 | runtime = "python3.8" 9 | publish = true 10 | 11 | # Real AWS infra 12 | # create_package = false 13 | # s3_existing_package = { 14 | # bucket = "fixtures" 15 | # key = "python3.8-zip/existing_package.zip" 16 | # } 17 | 18 | # LocalStack AWS infra 19 | # awslocal s3 mb s3://fixtures 20 | # awslocal s3 cp /Users/Bob/Sites/terraform-aws-modules/terraform-aws-lambda/examples/fixtures/python3.8-zip/existing_package.zip s3://fixtures/python3.8-zip/existing_package.zip 21 | create_package = false 22 | s3_existing_package = { 23 | bucket = "fixtures" 24 | key = "python3.8-zip/existing_package.zip" 25 | } 26 | 27 | # LocalStack hot swap 28 | # create_package = true 29 | # source_path = "/Users/Bob/Sites/terraform-aws-modules/terraform-aws-lambda/examples/fixtures/python3.8-app1" 30 | 31 | # create_package = false 32 | # s3_existing_package = { 33 | # bucket = "__local__" 34 | # key = "/Users/Bob/Sites/terraform-aws-modules/terraform-aws-lambda/examples/fixtures/python3.8-app1" 35 | # } 36 | # s3_bucket = "__local__" 37 | # s3_prefix = "/Users/Bob/Sites/terraform-aws-modules/terraform-aws-lambda/examples/fixtures/python3.8-app1" 38 | 39 | # ephemeral_storage_size = null 40 | 41 | attach_tracing_policy = true 42 | attach_policy_statements = true 43 | 44 | policy_statements = { 45 | dynamodb_read = { 46 | effect = "Allow", 47 | actions = ["dynamodb:GetItem"], 48 | resources = [module.dynamodb_table.dynamodb_table_arn] 49 | } 50 | } 51 | 52 | allowed_triggers = { 53 | AllowExecutionFromAPIGateway = { 54 | service = "apigateway" 55 | source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*/*" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nicconf/lambda_post.tf: -------------------------------------------------------------------------------- 1 | module "lambda_post" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 3.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-post" 6 | description = "My awesome Python lambda function" 7 | handler = "index.lambda_handler" 8 | runtime = "python3.8" 9 | publish = true 10 | 11 | create_package = false 12 | s3_existing_package = { 13 | bucket = "fixtures" 14 | key = "python3.8-zip/existing_package.zip" 15 | } 16 | 17 | # Free TACOS don't have Python available, so we can't build natively there. 18 | # source_path = "../src/python-function" 19 | # hash_extra = "post" 20 | 21 | attach_tracing_policy = true 22 | attach_policy_statements = true 23 | 24 | policy_statements = { 25 | dynamodb_write = { 26 | effect = "Allow", 27 | actions = ["dynamodb:PutItem"], 28 | resources = [module.dynamodb_table.dynamodb_table_arn] 29 | } 30 | } 31 | 32 | allowed_triggers = { 33 | AllowExecutionFromAPIGateway = { 34 | service = "apigateway" 35 | source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*/*" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /nicconf/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | # Make it faster by skipping something 5 | skip_get_ec2_platforms = true 6 | skip_metadata_api_check = true 7 | skip_region_validation = true 8 | skip_credentials_validation = true 9 | 10 | # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn 11 | skip_requesting_account_id = false 12 | } 13 | 14 | resource "random_pet" "this" { 15 | length = 2 16 | } 17 | -------------------------------------------------------------------------------- /nicconf/outputs.tf: -------------------------------------------------------------------------------- 1 | ################## 2 | # Lambda Function 3 | ################## 4 | output "lambda_function_arn" { 5 | description = "The ARN of the Lambda Function" 6 | value = module.lambda_get.lambda_function_arn 7 | } 8 | 9 | output "lambda_function_invoke_arn" { 10 | description = "The Invoke ARN of the Lambda Function" 11 | value = module.lambda_get.lambda_function_invoke_arn 12 | } 13 | 14 | output "lambda_function_name" { 15 | description = "The name of the Lambda Function" 16 | value = module.lambda_get.lambda_function_name 17 | } 18 | 19 | output "lambda_function_qualified_arn" { 20 | description = "The ARN identifying your Lambda Function Version" 21 | value = module.lambda_get.lambda_function_qualified_arn 22 | } 23 | 24 | output "lambda_function_version" { 25 | description = "Latest published version of Lambda Function" 26 | value = module.lambda_get.lambda_function_version 27 | } 28 | 29 | 30 | ############### 31 | # API Gateway 32 | ############### 33 | output "apigatewayv2_api_id" { 34 | description = "The API identifier" 35 | value = module.api_gateway.apigatewayv2_api_id 36 | } 37 | 38 | output "apigatewayv2_api_api_endpoint" { 39 | description = "The URI of the API" 40 | value = module.api_gateway.apigatewayv2_api_api_endpoint 41 | } 42 | 43 | output "apigatewayv2_api_arn" { 44 | description = "The ARN of the API" 45 | value = module.api_gateway.apigatewayv2_api_arn 46 | } 47 | 48 | output "apigatewayv2_api_execution_arn" { 49 | description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." 50 | value = module.api_gateway.apigatewayv2_api_execution_arn 51 | } 52 | 53 | output "default_apigatewayv2_stage_execution_arn" { 54 | description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." 55 | value = module.api_gateway.default_apigatewayv2_stage_execution_arn 56 | } 57 | 58 | 59 | ################# 60 | # DynamoDB Table 61 | ################# 62 | output "dynamodb_table_arn" { 63 | description = "ARN of the DynamoDB table" 64 | value = module.dynamodb_table.dynamodb_table_arn 65 | } 66 | 67 | output "dynamodb_table_id" { 68 | description = "ID of the DynamoDB table" 69 | value = module.dynamodb_table.dynamodb_table_id 70 | } 71 | 72 | # API Gateway - Domain name 73 | output "apigatewayv2_domain_name_id" { 74 | description = "The domain name identifier" 75 | value = module.api_gateway.apigatewayv2_domain_name_id 76 | } 77 | 78 | output "apigatewayv2_domain_name_configuration" { 79 | description = "The domain name configuration" 80 | value = module.api_gateway.apigatewayv2_domain_name_configuration 81 | } 82 | 83 | output "apigatewayv2_target_domain_name" { 84 | description = "The target domain name" 85 | value = module.api_gateway.apigatewayv2_domain_name_target_domain_name 86 | } 87 | 88 | output "apigatewayv2_hosted_zone_id" { 89 | description = "The Amazon Route 53 Hosted Zone ID of the endpoint" 90 | value = module.api_gateway.apigatewayv2_domain_name_hosted_zone_id 91 | } 92 | 93 | # CLI command 94 | output "command" { 95 | description = "CLI command to call API Gateway" 96 | value = "http GET ${module.api_gateway.apigatewayv2_api_api_endpoint}" 97 | } 98 | -------------------------------------------------------------------------------- /open@amazon/AWS-Serverless-Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/e5b25c431503b22cd216f47bf523300d4738d734/open@amazon/AWS-Serverless-Architecture.png -------------------------------------------------------------------------------- /open@amazon/README.md: -------------------------------------------------------------------------------- 1 | # Code for my talk "Why and how to use Terraform AWS modules?" 2 | 3 | This repository contains code for my talk at Open@Amazon 2021: 4 | 5 | The architecture created by this code ([source](https://ordina-jworks.github.io/cloud/2019/01/14/Infrastructure-as-code-with-terraform-and-aws-serverless.html)): 6 | 7 | ![AWS-Serverless-Architecture](https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/master/hashitalks2021/AWS-Serverless-Architecture.png) 8 | 9 | 10 | ## Getting started 11 | 12 | Run `terraform init` and `terraform apply` to get everything created. 13 | 14 | Call API Gateway endpoint using GET or POST methods, for eg: 15 | 16 | ``` 17 | $ http GET $(terraform output -raw apigatewayv2_api_api_endpoint) 18 | ``` 19 | 20 | 21 | ## How to update and deploy changes? 22 | 23 | 1. Update source code of the Lambda Functions inside `../src/python-function` 24 | 2. Run `terraform apply` to rebuild Lambda Function package (if necessary) and update the dependencies. 25 | 26 | PS: There is a way to do complex deployments of AWS Lambda functions using AWS CodeDeploy service, see [tmp-deploy directory for code](https://github.com/antonbabenko/serverless.tf-playground/tree/master/hashitalks2021/tmp-deploy). 27 | 28 | 29 | 30 | 31 | ## Requirements 32 | 33 | No requirements. 34 | 35 | ## Providers 36 | 37 | | Name | Version | 38 | |------|---------| 39 | | [random](#provider\_random) | n/a | 40 | 41 | ## Modules 42 | 43 | | Name | Source | Version | 44 | |------|--------|---------| 45 | | [api\_gateway](#module\_api\_gateway) | terraform-aws-modules/apigateway-v2/aws | ~> 1.0 | 46 | | [dynamodb\_table](#module\_dynamodb\_table) | terraform-aws-modules/dynamodb-table/aws | ~> 1.0 | 47 | | [lambda\_get](#module\_lambda\_get) | terraform-aws-modules/lambda/aws | ~> 2.0 | 48 | | [lambda\_post](#module\_lambda\_post) | terraform-aws-modules/lambda/aws | ~> 2.0 | 49 | 50 | ## Resources 51 | 52 | | Name | Type | 53 | |------|------| 54 | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | 55 | 56 | ## Inputs 57 | 58 | No inputs. 59 | 60 | ## Outputs 61 | 62 | | Name | Description | 63 | |------|-------------| 64 | | [apigatewayv2\_api\_api\_endpoint](#output\_apigatewayv2\_api\_api\_endpoint) | The URI of the API | 65 | | [apigatewayv2\_api\_arn](#output\_apigatewayv2\_api\_arn) | The ARN of the API | 66 | | [apigatewayv2\_api\_execution\_arn](#output\_apigatewayv2\_api\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 67 | | [apigatewayv2\_api\_id](#output\_apigatewayv2\_api\_id) | The API identifier | 68 | | [apigatewayv2\_domain\_name\_configuration](#output\_apigatewayv2\_domain\_name\_configuration) | The domain name configuration | 69 | | [apigatewayv2\_domain\_name\_id](#output\_apigatewayv2\_domain\_name\_id) | The domain name identifier | 70 | | [apigatewayv2\_hosted\_zone\_id](#output\_apigatewayv2\_hosted\_zone\_id) | The Amazon Route 53 Hosted Zone ID of the endpoint | 71 | | [apigatewayv2\_target\_domain\_name](#output\_apigatewayv2\_target\_domain\_name) | The target domain name | 72 | | [command](#output\_command) | CLI command to call API Gateway | 73 | | [default\_apigatewayv2\_stage\_execution\_arn](#output\_default\_apigatewayv2\_stage\_execution\_arn) | The ARN prefix to be used in an aws\_lambda\_permission's source\_arn attribute or in an aws\_iam\_policy to authorize access to the @connections API. | 74 | | [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | ARN of the DynamoDB table | 75 | | [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | ID of the DynamoDB table | 76 | | [lambda\_function\_arn](#output\_lambda\_function\_arn) | The ARN of the Lambda Function | 77 | | [lambda\_function\_invoke\_arn](#output\_lambda\_function\_invoke\_arn) | The Invoke ARN of the Lambda Function | 78 | | [lambda\_function\_name](#output\_lambda\_function\_name) | The name of the Lambda Function | 79 | | [lambda\_function\_qualified\_arn](#output\_lambda\_function\_qualified\_arn) | The ARN identifying your Lambda Function Version | 80 | | [lambda\_function\_version](#output\_lambda\_function\_version) | Latest published version of Lambda Function | 81 | 82 | -------------------------------------------------------------------------------- /open@amazon/alt_options/options_for_screenshots.tf: -------------------------------------------------------------------------------- 1 | # # Option 1: 2 | # # 1. ~40 resources + data-sources 3 | # # 2. 200 lines of code 4 | # # 5 | # # Lambda GET 6 | # resource "aws_lambda_function" "lambda_get" {} 7 | # resource "aws_lambda_permission" "lambda_get" {} 8 | # resource "aws_iam_role" "lambda_get" {} 9 | # resource "aws_iam_policy" "lambda_get" {} 10 | # resource "aws_iam_policy_attachment" "lambda_get" {} 11 | 12 | # # Lambda POST 13 | # resource "aws_lambda_function" "lambda_post" {} 14 | # resource "aws_lambda_permission" "lambda_post" {} 15 | # resource "aws_iam_role" "lambda_post" {} 16 | # resource "aws_iam_policy" "lambda_post" {} 17 | # resource "aws_iam_policy_attachment" "lambda_post" {} 18 | 19 | # # API Gateway 20 | # resource "aws_apigatewayv2_api" "this" {} 21 | # resource "aws_apigatewayv2_domain_name" "this" {} 22 | # resource "aws_apigatewayv2_api_mapping" "this" {} 23 | # resource "aws_apigatewayv2_route" "this" {} 24 | # resource "aws_apigatewayv2_integration" "this" {} 25 | 26 | # # DynamoDB 27 | # resource "aws_dynamodb_table" "this" {} 28 | 29 | 30 | 31 | # --- 32 | 33 | # # Option 2: 34 | # # 1. ~40 resources + data-sources 35 | # # 2. Add variables and outputs = 1000 lines of code 36 | 37 | # # variables.tf 38 | # variable "lambda_get_memory_size" { 39 | # description = "Memory size for get-record Lambda function" 40 | # default = 256 41 | # } 42 | 43 | # # Lambda GET 44 | # resource "aws_lambda_function" "lambda_get" { 45 | # function_name = "get-record" 46 | # memory_size = var.lambda_get_memory_size 47 | # # ... 48 | # } 49 | 50 | # resource "aws_lambda_permission" "lambda_get" {} 51 | # resource "aws_iam_role" "lambda_get" {} 52 | # resource "aws_iam_policy" "lambda_get" {} 53 | # resource "aws_iam_policy_attachment" "lambda_get" {} 54 | 55 | # # Lambda POST 56 | # resource "aws_lambda_function" "lambda_post" {} 57 | # resource "aws_lambda_permission" "lambda_post" {} 58 | # resource "aws_iam_role" "lambda_post" {} 59 | # resource "aws_iam_policy" "lambda_post" {} 60 | # resource "aws_iam_policy_attachment" "lambda_post" {} 61 | 62 | # # API Gateway 63 | # resource "aws_apigatewayv2_api" "this" {} 64 | # resource "aws_apigatewayv2_domain_name" "this" {} 65 | # resource "aws_apigatewayv2_api_mapping" "this" {} 66 | # resource "aws_apigatewayv2_route" "this" {} 67 | # resource "aws_apigatewayv2_integration" "this" {} 68 | 69 | # # DynamoDB 70 | # resource "aws_dynamodb_table" "this" {} 71 | 72 | 73 | # --- 74 | 75 | # # Option 3: 76 | # # 1. Move resources into custom Terraform modules 77 | # # 2. Reuse Terraform code for similar resources 78 | 79 | # # Lambda GET 80 | # module "lambda_get" { 81 | # source = "./lambda" 82 | 83 | # function_name = "get-record" 84 | # memory_size = var.lambda_get_memory_size 85 | # # ... values for IAM role, policies, permissions... 86 | # } 87 | 88 | # # Lambda POST 89 | # module "lambda_post" { 90 | # source = "./lambda" 91 | 92 | # function_name = "post-record" 93 | # memory_size = var.lambda_post_memory_size 94 | # # ... values for IAM role, policies, permissions... 95 | # } 96 | 97 | # # API Gateway 98 | # module "api_gateway" {} 99 | 100 | # # DynamoDB 101 | # module "dynamodb_table" {} 102 | 103 | # --- 104 | 105 | # # Option 4: 106 | # # 1. Use terraform-aws-modules 107 | # # 2. Much more features than in custom modules 108 | # # 3. Your configuration values = 100 lines of code 109 | 110 | # # Lambda GET 111 | # module "lambda_get" { 112 | # source = "terraform-aws-modules/lambda/aws" 113 | 114 | # function_name = "get-record" 115 | # memory_size = 256 116 | # # ... values for IAM role, policies, permissions... 117 | # } 118 | 119 | # # Lambda POST 120 | # module "lambda_post" { 121 | # source = "terraform-aws-modules/lambda/aws" 122 | 123 | # function_name = "post-record" 124 | # memory_size = 512 125 | # # ... values for IAM role, policies, permissions... 126 | # } 127 | 128 | # # API Gateway 129 | # module "api_gateway" { 130 | # source = "terraform-aws-modules/apigateway-v2/aws" 131 | # } 132 | 133 | # # DynamoDB 134 | # module "dynamodb_table" { 135 | # source = "terraform-aws-modules/dynamodb-table/aws" 136 | # } 137 | -------------------------------------------------------------------------------- /open@amazon/api_gateway.tf: -------------------------------------------------------------------------------- 1 | module "api_gateway" { 2 | source = "terraform-aws-modules/apigateway-v2/aws" 3 | version = "~> 1.0" 4 | 5 | name = "${random_pet.this.id}-http" 6 | description = "My awesome HTTP API Gateway" 7 | protocol_type = "HTTP" 8 | 9 | create_api_domain_name = false 10 | 11 | integrations = { 12 | "GET /" = { 13 | lambda_arn = module.lambda_get.lambda_function_arn 14 | payload_format_version = "2.0" 15 | } 16 | 17 | "POST /" = { 18 | lambda_arn = module.lambda_post.lambda_function_arn 19 | payload_format_version = "2.0" 20 | } 21 | 22 | "$default" = { 23 | lambda_arn = module.lambda_get.lambda_function_arn 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /open@amazon/dynamodb.tf: -------------------------------------------------------------------------------- 1 | module "dynamodb_table" { 2 | source = "terraform-aws-modules/dynamodb-table/aws" 3 | version = "~> 1.0" 4 | 5 | name = random_pet.this.id 6 | hash_key = "id" 7 | range_key = "name" 8 | 9 | attributes = [ 10 | { 11 | name = "id" 12 | type = "S" 13 | }, 14 | { 15 | name = "name" 16 | type = "S" 17 | }, 18 | ] 19 | } -------------------------------------------------------------------------------- /open@amazon/lambda_get.tf: -------------------------------------------------------------------------------- 1 | module "lambda_get" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 2.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-get" 6 | description = "My awesome Python lambda function" 7 | handler = "index.lambda_handler" 8 | runtime = "python3.8" 9 | publish = true 10 | 11 | create_package = false 12 | s3_existing_package = { 13 | bucket = "fixtures" 14 | key = "python3.8-zip/existing_package.zip" 15 | } 16 | 17 | # Free TACOS don't have Python available, so we can't build natively there. 18 | # source_path = "../src/python-function" 19 | # hash_extra = "get" 20 | 21 | attach_tracing_policy = true 22 | attach_policy_statements = true 23 | 24 | policy_statements = { 25 | dynamodb_read = { 26 | effect = "Allow", 27 | actions = ["dynamodb:GetItem"], 28 | resources = [module.dynamodb_table.dynamodb_table_arn] 29 | } 30 | } 31 | 32 | allowed_triggers = { 33 | AllowExecutionFromAPIGateway = { 34 | service = "apigateway" 35 | source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*/*" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /open@amazon/lambda_post.tf: -------------------------------------------------------------------------------- 1 | module "lambda_post" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 2.0" 4 | 5 | function_name = "${random_pet.this.id}-lambda-post" 6 | description = "My awesome Python lambda function" 7 | handler = "index.lambda_handler" 8 | runtime = "python3.8" 9 | publish = true 10 | 11 | create_package = false 12 | s3_existing_package = { 13 | bucket = "fixtures" 14 | key = "python3.8-zip/existing_package.zip" 15 | } 16 | 17 | # Free TACOS don't have Python available, so we can't build natively there. 18 | # source_path = "../src/python-function" 19 | # hash_extra = "post" 20 | 21 | attach_tracing_policy = true 22 | attach_policy_statements = true 23 | 24 | policy_statements = { 25 | dynamodb_write = { 26 | effect = "Allow", 27 | actions = ["dynamodb:PutItem"], 28 | resources = [module.dynamodb_table.dynamodb_table_arn] 29 | } 30 | } 31 | 32 | allowed_triggers = { 33 | AllowExecutionFromAPIGateway = { 34 | service = "apigateway" 35 | source_arn = "${module.api_gateway.apigatewayv2_api_execution_arn}/*/*/*" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /open@amazon/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | # Make it faster by skipping something 5 | skip_get_ec2_platforms = true 6 | skip_metadata_api_check = true 7 | skip_region_validation = true 8 | skip_credentials_validation = true 9 | 10 | # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn 11 | skip_requesting_account_id = false 12 | } 13 | 14 | resource "random_pet" "this" { 15 | length = 2 16 | } 17 | -------------------------------------------------------------------------------- /open@amazon/open@amazon-terraform-aws-modules-june-2021.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/serverless.tf-playground/e5b25c431503b22cd216f47bf523300d4738d734/open@amazon/open@amazon-terraform-aws-modules-june-2021.pdf -------------------------------------------------------------------------------- /open@amazon/outputs.tf: -------------------------------------------------------------------------------- 1 | ################## 2 | # Lambda Function 3 | ################## 4 | output "lambda_function_arn" { 5 | description = "The ARN of the Lambda Function" 6 | value = module.lambda_get.lambda_function_arn 7 | } 8 | 9 | output "lambda_function_invoke_arn" { 10 | description = "The Invoke ARN of the Lambda Function" 11 | value = module.lambda_get.lambda_function_invoke_arn 12 | } 13 | 14 | output "lambda_function_name" { 15 | description = "The name of the Lambda Function" 16 | value = module.lambda_get.lambda_function_name 17 | } 18 | 19 | output "lambda_function_qualified_arn" { 20 | description = "The ARN identifying your Lambda Function Version" 21 | value = module.lambda_get.lambda_function_qualified_arn 22 | } 23 | 24 | output "lambda_function_version" { 25 | description = "Latest published version of Lambda Function" 26 | value = module.lambda_get.lambda_function_version 27 | } 28 | 29 | 30 | ############### 31 | # API Gateway 32 | ############### 33 | output "apigatewayv2_api_id" { 34 | description = "The API identifier" 35 | value = module.api_gateway.apigatewayv2_api_id 36 | } 37 | 38 | output "apigatewayv2_api_api_endpoint" { 39 | description = "The URI of the API" 40 | value = module.api_gateway.apigatewayv2_api_api_endpoint 41 | } 42 | 43 | output "apigatewayv2_api_arn" { 44 | description = "The ARN of the API" 45 | value = module.api_gateway.apigatewayv2_api_arn 46 | } 47 | 48 | output "apigatewayv2_api_execution_arn" { 49 | description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." 50 | value = module.api_gateway.apigatewayv2_api_execution_arn 51 | } 52 | 53 | output "default_apigatewayv2_stage_execution_arn" { 54 | description = "The ARN prefix to be used in an aws_lambda_permission's source_arn attribute or in an aws_iam_policy to authorize access to the @connections API." 55 | value = module.api_gateway.default_apigatewayv2_stage_execution_arn 56 | } 57 | 58 | 59 | ################# 60 | # DynamoDB Table 61 | ################# 62 | output "dynamodb_table_arn" { 63 | description = "ARN of the DynamoDB table" 64 | value = module.dynamodb_table.dynamodb_table_arn 65 | } 66 | 67 | output "dynamodb_table_id" { 68 | description = "ID of the DynamoDB table" 69 | value = module.dynamodb_table.dynamodb_table_id 70 | } 71 | 72 | # API Gateway - Domain name 73 | output "apigatewayv2_domain_name_id" { 74 | description = "The domain name identifier" 75 | value = module.api_gateway.apigatewayv2_domain_name_id 76 | } 77 | 78 | output "apigatewayv2_domain_name_configuration" { 79 | description = "The domain name configuration" 80 | value = module.api_gateway.apigatewayv2_domain_name_configuration 81 | } 82 | 83 | output "apigatewayv2_target_domain_name" { 84 | description = "The target domain name" 85 | value = module.api_gateway.apigatewayv2_domain_name_target_domain_name 86 | } 87 | 88 | output "apigatewayv2_hosted_zone_id" { 89 | description = "The Amazon Route 53 Hosted Zone ID of the endpoint" 90 | value = module.api_gateway.apigatewayv2_domain_name_hosted_zone_id 91 | } 92 | 93 | # CLI command 94 | output "command" { 95 | description = "CLI command to call API Gateway" 96 | value = "http GET ${module.api_gateway.apigatewayv2_api_api_endpoint}" 97 | } 98 | -------------------------------------------------------------------------------- /open@amazon/s3_bucket.tf: -------------------------------------------------------------------------------- 1 | # module "s3_bucket" { 2 | # source = "terraform-aws-modules/s3-bucket/aws" 3 | # version = "~> 2.0" 4 | 5 | # bucket = "${random_pet.this.id}-bucket" 6 | # acl = "private" 7 | # force_destroy = true 8 | 9 | # # S3 bucket-level Public Access Block configuration 10 | # block_public_acls = true 11 | # block_public_policy = true 12 | # ignore_public_acls = true 13 | # restrict_public_buckets = true 14 | # } 15 | -------------------------------------------------------------------------------- /rag/README.md: -------------------------------------------------------------------------------- 1 | # Implementing RAG using serverless.tf (Doing serverless with Terraform on AWS) 2 | 3 | > This talk will focus on the implementation of Retrieval-Augmented Generation (RAG) using a serverless approach with Terraform on AWS. The demo will showcase deploying a full RAG pipeline, integrating AI models with a retrieval system, and handling real-time queries using AWS Lambda, API Gateway, and DynamoDB—all managed through Terraform using open-source Terraform AWS modules and serverless.tf. 4 | 5 | https://www.meetup.com/advancedaws/events/302961381/ 6 | 7 | ## Scope 8 | 9 | Ask questions about Terraform states stored in S3. 10 | 11 | AWS Bedrock => RAG (Knowledge Base, Agents) to query terraform states from S3 12 | 13 | ``` 14 | https POST pe43dluxmzynvocrnkwl4y2n4y0mlzuv.lambda-url.us-east-1.on.aws/ q=="How many resources were deployed?" 15 | ``` 16 | 17 | ## Setup 18 | 19 | [uv](https://github.com/astral-sh/uv) is used. 20 | 21 | ``` 22 | cd rag 23 | . .venv/bin/activate 24 | 25 | uv pip install -r requirements.txt 26 | ``` 27 | 28 | ## Troubleshooting 29 | 30 | > ImportError: failed to find libmagic. Check your installation 31 | 32 | Solution: `brew install libmagic` 33 | 34 | 35 | ## Read more: 36 | 37 | https://catalog.workshops.aws/building-gen-ai-apps/en-US/chat-with-docs -------------------------------------------------------------------------------- /rag/agent_lambda.tf: -------------------------------------------------------------------------------- 1 | # Bedrock Agent 2 | module "agent_lambda_function" { 3 | source = "terraform-aws-modules/lambda/aws" 4 | version = "~> 7.0" 5 | 6 | function_name = "${random_pet.this.id}-bedrock-agent" 7 | description = "My awesome Python lambda function as agent" 8 | handler = "handler.lambda_handler" 9 | runtime = "python3.12" 10 | timeout = 60 11 | architectures = ["arm64"] 12 | 13 | create_lambda_function_url = false 14 | cloudwatch_logs_retention_in_days = 7 15 | 16 | source_path = "./src/bedrock_agent/handler.py" 17 | 18 | attach_policy_statements = true 19 | policy_statements = { 20 | bedrock = { 21 | effect = "Allow", 22 | actions = [ 23 | "bedrock:*" 24 | ], 25 | resources = ["*"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rag/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | region = "us-east-1" 3 | } 4 | 5 | provider "aws" { 6 | region = local.region 7 | } 8 | 9 | module "lambda_function" { 10 | source = "terraform-aws-modules/lambda/aws" 11 | version = "~> 7.0" 12 | 13 | function_name = "${random_pet.this.id}-bedrock-rag" 14 | description = "My awesome Python lambda function" 15 | handler = "kb_rag.lambda_handler" 16 | runtime = "python3.12" 17 | timeout = 60 18 | architectures = ["arm64"] 19 | 20 | create_lambda_function_url = true 21 | cloudwatch_logs_retention_in_days = 7 22 | 23 | source_path = "./src/kb_rag.py" 24 | 25 | attach_policy_statements = true 26 | policy_statements = { 27 | bedrock = { 28 | effect = "Allow", 29 | actions = [ 30 | "bedrock:PutUseCaseForModelAccess", 31 | "bedrock:GetUseCaseForModelAccess", 32 | "bedrock:DeleteFoundationModelAgreement", 33 | "bedrock:CreateAgent", 34 | "bedrock:GetFoundationModelAvailability", 35 | "bedrock:GetModelInvocationLoggingConfiguration", 36 | "bedrock:ListFoundationModelAgreementOffers", 37 | "bedrock:AssociateThirdPartyKnowledgeBase", 38 | "bedrock:DeleteModelInvocationLoggingConfiguration", 39 | "bedrock:ListKnowledgeBases", 40 | "bedrock:PutFoundationModelEntitlement", 41 | "bedrock:ListModelCustomizationJobs", 42 | "bedrock:ListAgents", 43 | "bedrock:ListProvisionedModelThroughputs", 44 | "bedrock:ListCustomModels", 45 | "bedrock:CreateKnowledgeBase", 46 | "bedrock:PutModelInvocationLoggingConfiguration", 47 | "bedrock:ListFoundationModels", 48 | "bedrock:CreateFoundationModelAgreement", 49 | "bedrock:InvokeModel", 50 | "bedrock:Retrieve", 51 | "bedrock:RetrieveAndGenerate" 52 | ], 53 | resources = ["*"] 54 | } 55 | } 56 | 57 | environment_variables = { 58 | KB_ID = aws_bedrockagent_knowledge_base.this.id 59 | REGION = local.region 60 | } 61 | } 62 | 63 | # Bedrock Knowledgebase 64 | resource "aws_bedrockagent_knowledge_base" "this" { 65 | name = "knowledge-base-quick-start-sx80g" 66 | role_arn = "arn:aws:iam::835367859851:role/service-role/AmazonBedrockExecutionRoleForKnowledgeBase_sx80g" 67 | 68 | knowledge_base_configuration { 69 | type = "VECTOR" 70 | vector_knowledge_base_configuration { 71 | embedding_model_arn = "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v1" 72 | } 73 | } 74 | 75 | storage_configuration { 76 | type = "OPENSEARCH_SERVERLESS" 77 | opensearch_serverless_configuration { 78 | collection_arn = "arn:aws:aoss:us-east-1:835367859851:collection/sv4pmipz38orb8xmj08j" 79 | vector_index_name = "bedrock-knowledge-base-default-index" 80 | field_mapping { 81 | metadata_field = "AMAZON_BEDROCK_METADATA" 82 | text_field = "AMAZON_BEDROCK_TEXT_CHUNK" 83 | vector_field = "bedrock-knowledge-base-default-vector" 84 | } 85 | } 86 | } 87 | } 88 | 89 | # todo: add KB data source with S3 bucket 90 | 91 | resource "random_pet" "this" { 92 | length = 2 93 | } 94 | -------------------------------------------------------------------------------- /rag/outputs.tf: -------------------------------------------------------------------------------- 1 | output "lambda_function_url" { 2 | description = "The URL of the Lambda Function" 3 | value = module.lambda_function.lambda_function_url 4 | } 5 | 6 | output "test_command" { 7 | value = <