├── terraform ├── 02_modules │ ├── 06_sources │ │ ├── .terraform-version │ │ └── remote.tf │ ├── 01_minimal │ │ ├── variables.tf │ │ ├── outputs.tf │ │ ├── README.md │ │ ├── main.tf │ │ └── versions.tf │ ├── 05_providers │ │ ├── 02_complex │ │ │ ├── module │ │ │ │ ├── variables.tf │ │ │ │ ├── outputs.tf │ │ │ │ └── main.tf │ │ │ └── complex.tf │ │ ├── 03_embedded │ │ │ ├── module │ │ │ │ ├── variables.tf │ │ │ │ ├── outputs.tf │ │ │ │ └── main.tf │ │ │ └── embedded.tf │ │ ├── stacking.md │ │ └── 01_simple │ │ │ └── providers.tf │ ├── 03_complete │ │ └── aws-s3-bucket │ │ │ ├── outputs.tf │ │ │ ├── versions.tf │ │ │ ├── main.tf │ │ │ ├── examples │ │ │ └── simple │ │ │ │ └── main.tf │ │ │ ├── variables.tf │ │ │ └── README.md │ ├── 02_root │ │ └── root.tf │ ├── 04_meta │ │ └── meta.tf │ ├── modules.md │ └── README.md ├── 03_example │ ├── infrastructure │ │ └── aws │ │ │ ├── .terraform-version │ │ │ ├── vpc.tf │ │ │ ├── data.tf │ │ │ ├── versions.tf │ │ │ ├── backend │ │ │ ├── backend.tf │ │ │ └── .terraform.lock.hcl │ │ │ ├── ecr.tf │ │ │ ├── acm.tf │ │ │ ├── .terraform.lock.hcl │ │ │ ├── alb.tf │ │ │ └── ecs.tf │ ├── diagram.png │ └── README.md ├── 00_hello_terraform │ ├── 01_workflow │ │ ├── workflow.tf │ │ └── workflow.md │ └── 00_setup │ │ ├── main.tf │ │ ├── Brewfile │ │ └── setup.md ├── 01_syntax │ ├── 07_Lifecycle │ │ ├── variables.tf │ │ └── lifecycle.tf │ ├── 05_Multi_Resource │ │ ├── data.tf │ │ └── multi_resource.tf │ ├── terraform.tfvars │ ├── 04_Data_Sources_and_Resources │ │ ├── Screen Shot 2023-04-05 at 12.14.15 PM.png │ │ ├── resources.tf │ │ └── graph.svg │ ├── 09_Refactor │ │ └── refactor.tf │ ├── 00_HCL │ │ ├── example.hcl │ │ ├── example.hcl.yaml │ │ ├── example.hcl.json │ │ ├── hcl.hcl │ │ └── README.md │ ├── variables.tf │ ├── main.tf │ ├── 08_Provisioners │ │ └── provisioners.tf │ ├── 06_Dynamic_Blocks │ │ └── dynamic_blocks.tf │ ├── 03_Input_and_Output │ │ └── io.tf │ ├── 02_Configuration │ │ └── configuration.tf │ ├── syntax.md │ └── 01_Expressions │ │ └── terraform.tf └── 04_extras │ ├── 04_cdktf │ ├── aws │ │ ├── .gitignore │ │ ├── Pipfile │ │ ├── cdktf.json │ │ ├── main.py │ │ ├── main-test.py │ │ ├── help │ │ └── Pipfile.lock │ ├── docker │ │ ├── .gitignore │ │ ├── Pipfile │ │ ├── cdktf.json │ │ ├── main.py │ │ ├── main-test.py │ │ ├── help │ │ └── Pipfile.lock │ └── cdktf.md │ ├── 02_refactor │ └── refactor.md │ ├── 00_multi_region │ └── multi_region.md │ ├── 01_import │ └── import.md │ └── 03_collaboration │ ├── 02_continuous_deployment │ └── cd.md │ ├── 00_modules │ └── ci.md │ ├── 01_terraform_live │ └── repo.md │ └── 03_remote_state │ └── remote_state.md ├── slides └── slides.pptx ├── .gitignore └── .devcontainer └── devcontainer.json /terraform/02_modules/06_sources/.terraform-version: -------------------------------------------------------------------------------- 1 | 0.12.0 -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/.terraform-version: -------------------------------------------------------------------------------- 1 | 1.3.4 2 | -------------------------------------------------------------------------------- /terraform/02_modules/01_minimal/variables.tf: -------------------------------------------------------------------------------- 1 | variable "parameter" {} 2 | -------------------------------------------------------------------------------- /terraform/00_hello_terraform/01_workflow/workflow.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "bucket" {} 2 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/02_complex/module/variables.tf: -------------------------------------------------------------------------------- 1 | variable "parameter" {} 2 | -------------------------------------------------------------------------------- /terraform/00_hello_terraform/00_setup/main.tf: -------------------------------------------------------------------------------- 1 | output "greeting" { 2 | value = "Hello Terraform." 3 | } -------------------------------------------------------------------------------- /terraform/02_modules/01_minimal/outputs.tf: -------------------------------------------------------------------------------- 1 | output "result" { 2 | value = null_resource.example 3 | } 4 | -------------------------------------------------------------------------------- /terraform/02_modules/01_minimal/README.md: -------------------------------------------------------------------------------- 1 | # Minimal 2 | This is an example module with the minimum set of configs. -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/03_embedded/module/variables.tf: -------------------------------------------------------------------------------- 1 | variable "parameter" {} 2 | variable "region" {} 3 | -------------------------------------------------------------------------------- /terraform/01_syntax/07_Lifecycle/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bucket_name" {} 2 | variable "bucket_prefix" { default = null } 3 | -------------------------------------------------------------------------------- /slides/slides.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bananalab/Learn-Infrastructure-as-Code-with-Terraform/HEAD/slides/slides.pptx -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/02_complex/module/outputs.tf: -------------------------------------------------------------------------------- 1 | output "result" { 2 | value = null_resource.example 3 | } 4 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/03_embedded/module/outputs.tf: -------------------------------------------------------------------------------- 1 | output "result" { 2 | value = aws_s3_bucket.this 3 | } 4 | -------------------------------------------------------------------------------- /terraform/02_modules/01_minimal/main.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "example" { 2 | triggers = { 3 | trigger = var.parameter 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /terraform/03_example/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bananalab/Learn-Infrastructure-as-Code-with-Terraform/HEAD/terraform/03_example/diagram.png -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | imports/* 3 | !imports/__init__.py 4 | .terraform 5 | cdktf.out 6 | cdktf.log 7 | *terraform.*.tfstate* -------------------------------------------------------------------------------- /terraform/02_modules/01_minimal/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | null = { 4 | source = "hashicorp/null" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/03_embedded/module/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | resource "aws_s3_bucket" "this" { 6 | } 7 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | imports/* 3 | !imports/__init__.py 4 | .terraform 5 | cdktf.out 6 | cdktf.log 7 | *terraform.*.tfstate* -------------------------------------------------------------------------------- /terraform/01_syntax/05_Multi_Resource/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_availability_zones" "available" { 2 | state = "available" 3 | } 4 | 5 | data "aws_caller_identity" "current" {} 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .terraform 4 | terraform.tfstate 5 | terraform.tfstate.backup 6 | .terraform.lock.hcl 7 | *.tfplan 8 | src.zip 9 | Brewfile.lock.json 10 | .envrc_local -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/outputs.tf: -------------------------------------------------------------------------------- 1 | output "result" { 2 | description = <<-EOT 3 | The result of the module. 4 | EOT 5 | value = aws_s3_bucket.this 6 | } 7 | -------------------------------------------------------------------------------- /terraform/01_syntax/terraform.tfvars: -------------------------------------------------------------------------------- 1 | rules = [{ 2 | port = 443 3 | description = "Ingress for HTTPS" 4 | cidr = "0.0.0.0/0" 5 | }, 6 | { 7 | port = 80 8 | description = "Ingress for HTTP" 9 | }] 10 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [requires] 7 | python_version = "3" 8 | 9 | [packages] 10 | cdktf = "~=0.12.2" 11 | pytest = "*" 12 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/stacking.md: -------------------------------------------------------------------------------- 1 | # Provider stacking. 2 | 3 | The code for this demo can be found [here](https://github.com/bananalab/terraform-modules/tree/main/modules): https://github.com/bananalab/terraform-modules/tree/main/modules -------------------------------------------------------------------------------- /terraform/01_syntax/04_Data_Sources_and_Resources/Screen Shot 2023-04-05 at 12.14.15 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bananalab/Learn-Infrastructure-as-Code-with-Terraform/HEAD/terraform/01_syntax/04_Data_Sources_and_Resources/Screen Shot 2023-04-05 at 12.14.15 PM.png -------------------------------------------------------------------------------- /terraform/01_syntax/09_Refactor/refactor.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "aws_s3_bucket" "a_bucket" { 3 | } 4 | 5 | # Refactoring can achived with the moved block or with 6 | # `terraform state mv` 7 | #moved { 8 | # from = aws_s3_bucket.a_bucket 9 | # to = aws_s3_bucket.moved_bucket 10 | #} 11 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [requires] 7 | python_version = "3" 8 | 9 | [packages] 10 | cdktf = "~=0.12.0" 11 | pytest = "*" 12 | cdktf-cdktf-provider-docker = "~=2.0.58" 13 | -------------------------------------------------------------------------------- /terraform/02_modules/02_root/root.tf: -------------------------------------------------------------------------------- 1 | # The root module (aka config) refers to a module similar to the 2 | # resource syntax, but not exactly the same. 3 | 4 | module "minimal" { 5 | source = "../01_minimal" 6 | parameter = "test" 7 | } 8 | 9 | output "result" { 10 | value = module.minimal.result 11 | } 12 | -------------------------------------------------------------------------------- /terraform/01_syntax/00_HCL/example.hcl: -------------------------------------------------------------------------------- 1 | io_mode = "async" 2 | 3 | service "http" "web_proxy" { 4 | listen_addr = "127.0.0.1:8080" 5 | 6 | process "main" { 7 | command = ["/usr/local/bin/awesome-app", "server"] 8 | } 9 | 10 | process "mgmt" { 11 | command = ["/usr/local/bin/awesome-app", "mgmt"] 12 | } 13 | } -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/vpc.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "github.com/bananalab/terraform-modules//modules/aws-vpc?ref=v0.4.1" 3 | cidr = "10.0.0.0/16" 4 | availability_zones = ["us-west-1b", "us-west-1c"] 5 | public_subnets = ["10.0.0.0/20", "10.0.16.0/20"] 6 | private_subnets = ["10.0.32.0/20", "10.0.48.0/20"] 7 | } 8 | -------------------------------------------------------------------------------- /terraform/01_syntax/00_HCL/example.hcl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | io_mode: async 3 | service: 4 | http: 5 | web_proxy: 6 | listen_addr: 127.0.0.1:8080 7 | process: 8 | main: 9 | command: 10 | - "/usr/local/bin/awesome-app" 11 | - server 12 | mgmt: 13 | command: 14 | - "/usr/local/bin/awesome-app" 15 | - mgmt 16 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/data.tf: -------------------------------------------------------------------------------- 1 | # Place common data sources in this file. 2 | 3 | data "aws_availability_zones" "available" { 4 | state = "available" 5 | } 6 | 7 | data "aws_caller_identity" "current" {} 8 | 9 | data "aws_route53_zone" "bananalab" { 10 | name = "bananalab.dev." 11 | } 12 | 13 | locals { 14 | app_host = "learn-terraform.${data.aws_route53_zone.bananalab.name}" 15 | } 16 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/cdktf.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "python", 3 | "app": "pipenv run python main.py", 4 | "projectId": "5274445f-2acf-4a7e-a552-dd84a79e1d90", 5 | "sendCrashReports": "true", 6 | "terraformProviders": [], 7 | "terraformModules": [], 8 | "codeMakerOutput": "imports", 9 | "context": { 10 | "excludeStackIdFromLogicalIds": "true", 11 | "allowSepCharsInLogicalIds": "true" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/02_complex/module/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.0" 6 | configuration_aliases = [aws.west, aws.east] 7 | } 8 | } 9 | } 10 | 11 | resource "aws_s3_bucket" "west" { 12 | provider = aws.west 13 | } 14 | 15 | resource "aws_s3_bucket" "east" { 16 | provider = aws.east 17 | } 18 | -------------------------------------------------------------------------------- /terraform/01_syntax/variables.tf: -------------------------------------------------------------------------------- 1 | variable "rules" { 2 | type = list(object({ 3 | port = number 4 | description = string 5 | cidr = string 6 | })) 7 | /*default = [{ 8 | port = 443 9 | description = "Ingress for HTTPS" 10 | cidr = "0.0.0.0/0" 11 | }, 12 | { 13 | port = 80 14 | description = "Ingress for HTTP" 15 | cidr = "0.0.0.0/0" 16 | }]*/ 17 | } 18 | -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * 6 | */ 7 | 8 | terraform { 9 | required_providers { 10 | aws = { 11 | source = "hashicorp/aws" 12 | version = "~>4" 13 | } 14 | } 15 | required_version = ">= 1.2.0" 16 | } 17 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/cdktf.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "python", 3 | "app": "pipenv run python main.py", 4 | "projectId": "5bca61b9-e789-4b2c-b632-8ece728ff41a", 5 | "sendCrashReports": "true", 6 | "terraformProviders": [ 7 | "hashicorp/aws@~>4.0" 8 | ], 9 | "terraformModules": [ 10 | "terraform-aws-modules/vpc/aws@2.77.0" 11 | ], 12 | "codeMakerOutput": "imports", 13 | "context": { 14 | "excludeStackIdFromLogicalIds": "true", 15 | "allowSepCharsInLogicalIds": "true" 16 | } 17 | } -------------------------------------------------------------------------------- /terraform/04_extras/02_refactor/refactor.md: -------------------------------------------------------------------------------- 1 | # Refactoring Terraform. 2 | 3 | Somtimes Terraform code needs to evolve to grow. Terraform provides some tools to help with this. 4 | 5 | 1. The `moved` block. 6 | Within a terraform config you can specify the new path for an object: 7 | ``` 8 | moved { 9 | 10 | 11 | } 12 | ``` 13 | 2. The command `terraform state mv` 14 | 15 | Try refactoring your imported code into mutiple state files or multiple modules (or both). 16 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "learn-terraform-202151242785" 4 | dynamodb_table = "learn-terraform-lock" 5 | key = "terraform.tfstate" 6 | region = "us-west-1" 7 | } 8 | required_providers { 9 | aws = { 10 | source = "hashicorp/aws" 11 | version = "~> 4.0" 12 | } 13 | } 14 | } 15 | 16 | provider "aws" { 17 | default_tags { 18 | tags = { 19 | terraformed = true 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /terraform/04_extras/00_multi_region/multi_region.md: -------------------------------------------------------------------------------- 1 | # Multi region resources 2 | 3 | Often resources Terraform is used to manage resources in multiple regions. 4 | 5 | Look at the example here: https://github.com/bananalab/terraform-modules/tree/main/modules/aws-rds-global-cluster 6 | 7 | See that the module creates a database cluster spanning 2 regions. 8 | 9 | Challenge: 10 | 1. Clone the repo and see if you can fix any deploy and pre-commit errors. 11 | 2. Can you make the cluster span 3 regions? 12 | 3. Can you make the cluster span N number of regions? -------------------------------------------------------------------------------- /terraform/02_modules/04_meta/meta.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "example" {} 2 | 3 | # Explicit dependency 4 | # Same as resource or data_source 5 | module "depends_on" { 6 | source = "../01_minimal" 7 | parameter = "test" 8 | depends_on = [null_resource.example] 9 | } 10 | 11 | # Count 12 | module "count" { 13 | count = 0 14 | source = "../01_minimal" 15 | parameter = count.index 16 | } 17 | 18 | # For each 19 | module "for_each" { 20 | for_each = toset([for x in range(10) : tostring(x)]) 21 | source = "../01_minimal" 22 | parameter = each.value 23 | } 24 | -------------------------------------------------------------------------------- /terraform/04_extras/01_import/import.md: -------------------------------------------------------------------------------- 1 | # Import resources 2 | 3 | When adopting Terraform resources often already exist. To import resources you can use the command `terraform import` however the terraform code that defines the infrastructure must already exist. 4 | 5 | Challenge: 6 | Move the state file from the `multi_region` excersize to `terraform.tfstate` 7 | 8 | Explore: 9 | Importing infrastructure is very tedious. Tools exists to assist with the process. See if you can import your infrastructure with [Terraformer](https://github.com/GoogleCloudPlatform/terraformer). 10 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devcontainer", 3 | "image": "bananalab/devcontainer", 4 | "mounts": [ 5 | "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" 6 | ], 7 | "containerUser": "devcontainer", 8 | "remoteUser": "devcontainer", 9 | "customizations": { 10 | "vscode": { 11 | "extensions": [ 12 | "hashicorp.hcl", 13 | "hashicorp.terraform" 14 | ], 15 | "settings": { 16 | "editor.formatOnPaste": true, 17 | "editor.formatOnSave": true 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /terraform/01_syntax/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "vpc" { 2 | cidr_block = "10.0.0.0/16" 3 | } 4 | resource "aws_security_group" "main" { 5 | name = "resource_with_dynamic_block" 6 | vpc_id = aws_vpc.vpc.id 7 | 8 | dynamic "ingress" { 9 | for_each = toset(var.rules) 10 | 11 | content { 12 | description = ingress.value.description 13 | from_port = ingress.value.port 14 | to_port = ingress.value.port 15 | protocol = "tcp" 16 | cidr_blocks = [ingress.value.cidr] 17 | } 18 | } 19 | 20 | tags = { 21 | Name = "AWS security group dynamic block" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /terraform/02_modules/modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | 1. Browse to https://github.com/bananalab/terraform-modules-template 4 | 2. Create your own repository from the template by clicking "Use this template" 5 | 3. Clone your new repo. 6 | 4. Change directory into your new repo. 7 | 5. Run `brew bundle` 8 | 5. Run `make module aws-s3-bucket` 9 | 6. Explore the files and directories created. 10 | 8. Code the Terraform module to create an S3 bucket. 11 | 9. run `pre-commit install` 12 | 9. Commit using a conventional commit formatted commit message (e.g. `feat(s3-bucket): New module`) and push the branch. 13 | 10. Fix any pre-commit issues. 14 | 11. Fix CI issues -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/cdktf.md: -------------------------------------------------------------------------------- 1 | # The Cloud Development Kit for Terraform 2 | CDKTF is a variant of the CDK that uses Terraform as it's execution engine rather than CloudFormation. 3 | 4 | Install 5 | ```bash 6 | brew install cdktf 7 | ``` 8 | or 9 | ```bash 10 | npm install cdktf-cli@0.12.2 -g 11 | ``` 12 | > Make sure version >= 0.12.2... There is a bug in 0.12.0 that prevents the AWS provider from working 13 | 14 | Create a project 15 | ```bash 16 | mkdir cdktf-lab && cd cdktf-lab 17 | cdktf init 18 | ``` 19 | 20 | Add the AWS provider 21 | ```bash 22 | cdktf provider add "aws@~>4.0" 23 | ``` 24 | 25 | Run `cdktf get` to download required modules and providers. 26 | 27 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/backend/backend.tf: -------------------------------------------------------------------------------- 1 | 2 | data "aws_caller_identity" "current" {} 3 | 4 | module "backend_s3_bucket" { 5 | source = "github.com/bananalab/terraform-modules//modules/aws-s3-bucket?ref=v0.3.1" 6 | bucket = "learn-terraform-${data.aws_caller_identity.current.account_id}" 7 | enable_replication = false 8 | logging_enabled = false 9 | } 10 | 11 | resource "aws_dynamodb_table" "this" { 12 | name = "learn-terraform-lock" 13 | billing_mode = "PROVISIONED" 14 | hash_key = "LockID" 15 | read_capacity = 20 16 | write_capacity = 20 17 | attribute { 18 | name = "LockID" 19 | type = "S" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /terraform/00_hello_terraform/00_setup/Brewfile: -------------------------------------------------------------------------------- 1 | # Install course requirements 2 | # cd into this directory and run. 3 | # `brew bundle` 4 | 5 | tap "homebrew/bundle" 6 | tap "homebrew/core" 7 | 8 | brew "checkov" # IaC policy check https://checkov.io 9 | brew "cloud-nuke" # Destroyer of clouds. 10 | brew "graphviz" # Graph visualizer 11 | brew "make" # Makefile processor 12 | brew "pre-commit" # Pre-commit static analysis 13 | brew "terraform-docs" # Automatic documentation for Terraform modules 14 | brew "terragrunt" # Terraform preprocessor to keep configs DRY. 15 | brew "tfenv" # Terraform version manager 16 | brew "tflint" # Terraform linter 17 | brew "kreuzwerker/taps/m1-terraform-provider-helper" # For m1 Mac compat. -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/03_embedded/embedded.tf: -------------------------------------------------------------------------------- 1 | # Providers can be embedded in modules, but this is to be avoided. 2 | # Embedding provider configuration is tempting becuase it seems to provide 3 | # an elegant level of abstration and encapsulation. 4 | 5 | data "aws_regions" "current" { 6 | all_regions = true 7 | } 8 | /* 9 | module "multi_region" { 10 | for_each = aws_regions.current.names 11 | source = "./module" 12 | parameter = 1 13 | region = each.value 14 | } 15 | */ 16 | module "west" { 17 | source = "./module" 18 | parameter = 1 19 | region = "us-west-1" 20 | } 21 | 22 | module "east" { 23 | source = "./module" 24 | parameter = 1 25 | region = "us-east-1" 26 | } 27 | -------------------------------------------------------------------------------- /terraform/04_extras/03_collaboration/02_continuous_deployment/cd.md: -------------------------------------------------------------------------------- 1 | # Continuous Deployment and Terraform 2 | 3 | When running Terraform in production it's wise to use a continuous delivery system to ensure consistency. 4 | 5 | A good open source option is [Atlantis](https://www.runatlantis.io/). Atlantis interacts with various Git providers by hooking into the collaborations features (Pull Request). 6 | 7 | # Set up Atlantis 8 | 9 | Follow the instructions [here](https://www.runatlantis.io/guide/testing-locally.html) to set up Atlantis for local testing. 10 | 11 | The Webhook should be configured in the `terraform-live` repo you created previously. 12 | 13 | Modify the code in `terraform-live` and push it to Github. What happens? -------------------------------------------------------------------------------- /terraform/01_syntax/08_Provisioners/provisioners.tf: -------------------------------------------------------------------------------- 1 | # Provisioner blocks 2 | # Used to configure resources after creation. 3 | # Example: 4 | # SSH into ec2 instance and configure a web server. 5 | # Provisioners are seldom used now. 6 | # use provisioners as a last resort. See: https://www.terraform.io/language/resources/provisioners/syntax 7 | 8 | # The most common use case for provisioners is executing miscellaneous scripts. 9 | resource "null_resource" "mkdir" { 10 | provisioner "local-exec" { 11 | command = "mkdir -p ${self.id}" 12 | #interpreter = ["/bin/bash", "-c"] 13 | #working_dir = "~" 14 | } 15 | 16 | provisioner "local-exec" { 17 | when = destroy 18 | command = "rm -rf ${self.id}" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /terraform/01_syntax/00_HCL/example.hcl.json: -------------------------------------------------------------------------------- 1 | { 2 | "io_mode": "async", 3 | "service": { 4 | "http": { 5 | "web_proxy": { 6 | "listen_addr": "127.0.0.1:8080", 7 | "process": { 8 | "main": { 9 | "command": [ 10 | "/usr/local/bin/awesome-app", 11 | "server" 12 | ] 13 | }, 14 | "mgmt": { 15 | "command": [ 16 | "/usr/local/bin/awesome-app", 17 | "mgmt" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/main.py: -------------------------------------------------------------------------------- 1 | from constructs import Construct 2 | from cdktf import App, TerraformStack 3 | from imports.aws import AwsProvider 4 | from imports.aws.sns import SnsTopic 5 | from imports.terraform_aws_modules.aws import Vpc 6 | 7 | 8 | class MyStack(TerraformStack): 9 | def __init__(self, scope: Construct, ns: str): 10 | super().__init__(scope, ns) 11 | 12 | AwsProvider(self, 'Aws', region='us-east-1') 13 | 14 | Vpc(self, 'CustomVpc', 15 | name='custom-vpc', 16 | cidr='10.0.0.0/16', 17 | azs=["us-east-1a", "us-east-1b"], 18 | public_subnets=["10.0.1.0/24", "10.0.2.0/24"] 19 | ) 20 | SnsTopic(self, 'Topic', display_name='my-first-sns-topic') 21 | 22 | app = App() 23 | MyStack(app, "python-aws") 24 | 25 | app.synth() 26 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/01_simple/providers.tf: -------------------------------------------------------------------------------- 1 | # Sometimes mutiple instances of a provider are needed. 2 | # Example: 3 | # Mutli region AWS 4 | provider "aws" { 5 | alias = "uswest1" 6 | region = "us-west-1" 7 | } 8 | 9 | provider "aws" { 10 | alias = "useast1" 11 | region = "us-east-1" 12 | } 13 | 14 | module "west" { 15 | providers = { 16 | aws = aws.uswest1 17 | } 18 | source = "../../01_minimal" 19 | parameter = 1 20 | } 21 | 22 | module "east" { 23 | providers = { 24 | aws = aws.useast1 25 | } 26 | source = "../../01_minimal" 27 | parameter = 2 28 | } 29 | 30 | /* This won't work, unfortunately. 31 | module "for_each" { 32 | for_each = [useast1, uswest1] 33 | providers = { 34 | aws = "aws.${each.value}" 35 | } 36 | source = "../../01_minimal" 37 | parameter = 3 38 | } 39 | */ 40 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from constructs import Construct 4 | from cdktf import App, TerraformStack 5 | from cdktf_cdktf_provider_docker import Image, Container, DockerProvider 6 | 7 | 8 | class MyStack(TerraformStack): 9 | def __init__(self, scope: Construct, ns: str): 10 | super().__init__(scope, ns) 11 | 12 | DockerProvider(self, 'docker') 13 | 14 | docker_image = Image(self, 'nginxImage', 15 | name='nginx:latest', 16 | keep_locally=False) 17 | 18 | Container(self, 'nginxContainer', 19 | name='tutorial', 20 | image=docker_image.name, 21 | ports=[{ 22 | 'internal': 80, 23 | 'external': 8000 24 | }]) 25 | 26 | 27 | app = App() 28 | MyStack(app, "learn-cdktf-docker") 29 | 30 | app.synth() 31 | -------------------------------------------------------------------------------- /terraform/03_example/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure 2 | 3 | ![diagram](diagram.png "Diagram") 4 | 5 | The infrastructure is defined in Terraform code found in the 6 | [infrastructure](infrastructure) directory. 7 | > Additional Terraform modules referenced can be found in the 8 | [terraform-modules repo](https://github.com/bananalab/terraform-modules) 9 | 10 | ### Infrastructure Deployment 11 | Before the first deployment the Terraform backend must be deployed: 12 | 13 | ```bash 14 | cd infrastructure/aws/backend 15 | terraform init 16 | terraform plan -out tfplan.out 17 | terraform apply tfplan.out 18 | ``` 19 | 20 | The infrastructure can be deployed with these commands 21 | 22 | ```bash 23 | cd infrastructure/aws 24 | terraform init 25 | terraform plan -out tfplan.out 26 | ``` 27 | 28 | Carefully review the plan output before proceeding to 29 | 30 | ```bash 31 | terraform apply tfplan.out 32 | ``` 33 | -------------------------------------------------------------------------------- /terraform/02_modules/05_providers/02_complex/complex.tf: -------------------------------------------------------------------------------- 1 | # Sometimes mutiple instances of a provider are needed for the same module. 2 | # Example: 3 | # Mutli region AWS 4 | 5 | terraform { 6 | required_providers { 7 | aws = { 8 | source = "hashicorp/aws" 9 | version = "~> 4.0" 10 | configuration_aliases = [aws.uswest1, aws.useast1] 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | alias = "uswest1" 17 | region = "us-west-1" 18 | } 19 | 20 | provider "aws" { 21 | alias = "useast1" 22 | region = "us-east-1" 23 | } 24 | 25 | module "multi_region" { 26 | providers = { 27 | aws.west = aws.uswest1 28 | aws.east = aws.useast1 29 | } 30 | source = "./module" 31 | parameter = 1 32 | } 33 | 34 | # Drawbacks: 35 | # * Complexity = Bad. 36 | # * Breaks encapsulation. 37 | # * "Code smell" (There is probably a better solution) 38 | -------------------------------------------------------------------------------- /terraform/04_extras/03_collaboration/00_modules/ci.md: -------------------------------------------------------------------------------- 1 | # Continuous integration with Terraform. 2 | 3 | Terraform is testable! Normally only reusable modules are tested (not root configs). 4 | 5 | The leading testing library for Terraform is [Terratest](https://terratest.gruntwork.io/) 6 | 7 | Terratest uses the Go test framework and provides classes specific to Terraform. 8 | 9 | Look in the `tests` directory in your modules. 10 | 11 | You can run the tests with `make test` inside your module's directory (e.g. modules/aws-s3-bucket). 12 | 13 | These tests are run in Github Actions when code is pushed to the modules repo. 14 | 15 | # Check it out. 16 | Push your module to your repo and look for the yellow or green dot. Is there a red X? 17 | 18 | Can you get your test to pass? 19 | 20 | Make changes to your module and check that tests still pass. 21 | 22 | Try setting your repo to require passing tests before merging pull requests. 23 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/ecr.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | repos = ["frontend", "backend"] 3 | } 4 | 5 | resource "aws_ecr_repository" "this" { 6 | for_each = toset(local.repos) 7 | name = "learn-terraform-devops-code-challenge-${each.value}" 8 | image_tag_mutability = "MUTABLE" 9 | encryption_configuration { 10 | encryption_type = "KMS" 11 | } 12 | image_scanning_configuration { 13 | scan_on_push = true 14 | } 15 | } 16 | 17 | resource "aws_ecr_lifecycle_policy" "main" { 18 | for_each = aws_ecr_repository.this 19 | repository = each.value.name 20 | 21 | policy = jsonencode({ 22 | rules = [{ 23 | rulePriority = 1 24 | description = "keep last 10 images" 25 | action = { 26 | type = "expire" 27 | } 28 | selection = { 29 | tagStatus = "any" 30 | countType = "imageCountMoreThan" 31 | countNumber = 10 32 | } 33 | }] 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/main-test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cdktf import Testing 3 | 4 | # The tests below are example tests, you can find more information at 5 | # https://cdk.tf/testing 6 | 7 | class TestMain: 8 | 9 | def test_my_app(self): 10 | assert True 11 | 12 | #stack = TerraformStack(Testing.app(), "stack") 13 | #app_abstraction = MyApplicationsAbstraction(stack, "app-abstraction") 14 | #synthesized = Testing.synth(stack) 15 | 16 | #def test_should_contain_container(self): 17 | # assert Testing.to_have_resource(self.synthesized, Container.TF_RESOURCE_TYPE) 18 | 19 | #def test_should_use_an_ubuntu_image(self): 20 | # assert Testing.to_have_resource_with_properties(self.synthesized, Image.TF_RESOURCE_TYPE, { 21 | # "name": "ubuntu:latest", 22 | # }) 23 | 24 | #def test_check_validity(self): 25 | # assert Testing.to_be_valid_terraform(Testing.full_synth(stack)) -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/main-test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from cdktf import Testing 3 | 4 | # The tests below are example tests, you can find more information at 5 | # https://cdk.tf/testing 6 | 7 | class TestMain: 8 | 9 | def test_my_app(self): 10 | assert True 11 | 12 | #stack = TerraformStack(Testing.app(), "stack") 13 | #app_abstraction = MyApplicationsAbstraction(stack, "app-abstraction") 14 | #synthesized = Testing.synth(stack) 15 | 16 | #def test_should_contain_container(self): 17 | # assert Testing.to_have_resource(self.synthesized, Container.TF_RESOURCE_TYPE) 18 | 19 | #def test_should_use_an_ubuntu_image(self): 20 | # assert Testing.to_have_resource_with_properties(self.synthesized, Image.TF_RESOURCE_TYPE, { 21 | # "name": "ubuntu:latest", 22 | # }) 23 | 24 | #def test_check_validity(self): 25 | # assert Testing.to_be_valid_terraform(Testing.full_synth(stack)) -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/acm.tf: -------------------------------------------------------------------------------- 1 | resource "aws_acm_certificate" "this" { 2 | domain_name = "learn-terraform.${data.aws_route53_zone.bananalab.name}" 3 | validation_method = "DNS" 4 | } 5 | 6 | resource "aws_route53_record" "validation" { 7 | for_each = { 8 | for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => { 9 | name = dvo.resource_record_name 10 | record = dvo.resource_record_value 11 | type = dvo.resource_record_type 12 | } 13 | } 14 | 15 | allow_overwrite = true 16 | name = each.value.name 17 | records = [each.value.record] 18 | ttl = 60 19 | type = each.value.type 20 | zone_id = data.aws_route53_zone.bananalab.zone_id 21 | } 22 | 23 | resource "aws_acm_certificate_validation" "this" { 24 | certificate_arn = aws_acm_certificate.this.arn 25 | validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn] 26 | } 27 | -------------------------------------------------------------------------------- /terraform/01_syntax/00_HCL/hcl.hcl: -------------------------------------------------------------------------------- 1 | # Generic HCL syntax 2 | type "label1" "label2" { 3 | block { 4 | attribute = "value" 5 | } 6 | attribute = "string value" 7 | attribute = true # bool 8 | attribute = ["list"] 9 | attribute = { 10 | map = true 11 | } 12 | } 13 | 14 | # Other HCL implementations 15 | 16 | ## Packer 17 | packer { 18 | required_plugins { 19 | docker = { 20 | version = ">= 0.0.7" 21 | source = "github.com/hashicorp/docker" 22 | } 23 | } 24 | } 25 | 26 | source "docker" "ubuntu" { 27 | image = "ubuntu:xenial" 28 | commit = true 29 | } 30 | 31 | build { 32 | name = "learn-packer" 33 | sources = [ 34 | "source.docker.ubuntu" 35 | ] 36 | } 37 | 38 | # Terragrunt 39 | include "root" { 40 | path = find_in_parent_folders() 41 | } 42 | 43 | include "envcommon" { 44 | path = "${dirname(find_in_parent_folders())}/_envcommon/webserver-cluster.hcl" 45 | } 46 | 47 | inputs = { 48 | instance_type = "t2.medium" 49 | 50 | min_size = 3 51 | max_size = 3 52 | } -------------------------------------------------------------------------------- /terraform/01_syntax/05_Multi_Resource/multi_resource.tf: -------------------------------------------------------------------------------- 1 | #Count 2 | # All resources have a `count` meta-parameter. 3 | # If count is set then a list of resources is returned (even if there is only 1) 4 | # If `count` is set then a `count.index` value is available. This value contains 5 | # the current iteration number. 6 | # TIP: setting `count = 0` is a handy way to remove a resource but keep the config. 7 | resource "aws_s3_bucket" "count_buckets" { 8 | count = 1 9 | # count = var.create_bucket == true ? 1 : 0 10 | bucket = "${data.aws_caller_identity.current.account_id}-bucket${count.index}" 11 | } 12 | 13 | 14 | #For Each 15 | # All resources may have a `for_each` meta parameter. 16 | # you can use for_each to iterate over any iterable item (list, set, map, or object). 17 | # An empty iterator won't create any resource. 18 | resource "aws_s3_bucket" "each_buckets" { 19 | for_each = toset(data.aws_availability_zones.available.names) 20 | 21 | bucket = "${data.aws_caller_identity.current.account_id}-${each.value}" 22 | tags = { 23 | description = each.value 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /terraform/04_extras/03_collaboration/01_terraform_live/repo.md: -------------------------------------------------------------------------------- 1 | # Terraform Live. 2 | 3 | We already created a Github repo for reusable modules, but what about root configs? 4 | 5 | Root configs represent the single source of truth for your infrastructure. The content of the HEAD of the default branch should always represent the desired state of the infrastructure. One way to think of it: If the infrastructure doesn't match the default branch, then the infrastructure is wrong. A `terraform apply` should always bring the infrastructure back to the desired state. This is why it's important that no one is allowed to modify infrastructure with the AWS console in production accounts. 6 | 7 | # Create a root config. 8 | 9 | 1. Create an empty repo called `terraform-live` in your Github account and clone it to your laptop. 10 | 11 | 2. Within the repo create a directory called `infra`. 12 | 13 | 3. Create the standard Terraform layout (`main.tf`, `variables.tf`, `outputs.tf` and `versions.tf`). 14 | 15 | 4. Add the Terraform code to create an s3 (hint: reuse the module you created in the previous excersise using it's Github path as the source.) -------------------------------------------------------------------------------- /terraform/02_modules/06_sources/remote.tf: -------------------------------------------------------------------------------- 1 | # Providers don't have to be in the local filesystem. 2 | 3 | # Terraform public registry 4 | module "public_registry" { 5 | source = "rojopolis/repo-index/helm" 6 | version = "0.1.0" 7 | dir = "" 8 | } 9 | 10 | # Terraform private registry 11 | /* 12 | module "private_registry" { 13 | source = "app.terraform.io/bananalab/repo-index/helm" 14 | version = "0.1.0" 15 | dir = "" 16 | } 17 | */ 18 | 19 | module "github" { 20 | source = "github.com/rojopolis/terraform-helm-repo-index?ref=0.1.0" 21 | dir = "" 22 | } 23 | 24 | module "generic_git_https" { 25 | source = "git::https://github.com/rojopolis/terraform-helm-repo-index.git?ref=0.1.0" 26 | dir = "" 27 | } 28 | 29 | module "generic_git_ssh" { 30 | source = "git::git@github.com:rojopolis/terraform-helm-repo-index.git?ref=0.1.0" 31 | dir = "" 32 | } 33 | 34 | 35 | module "github_monorepo" { 36 | source = "github.com/bananalab/terraform-modules//modules/aws-s3-bucket?ref=v0.1.0" 37 | 38 | } 39 | 40 | module "generic_git_ssh" { 41 | source = "git::git@github.com/bananalab/terraform-modules.git//modules/aws-s3-bucket?ref=v0.1.0" 42 | 43 | } 44 | -------------------------------------------------------------------------------- /terraform/00_hello_terraform/01_workflow/workflow.md: -------------------------------------------------------------------------------- 1 | # Terraform Workflow 2 | The core Terraform workflow consists of 3 phases: 3 | 4 | 1. Write 5 | 6 | Infrastructure code is written in Terraform's DSL called Hashicorp 7 | configuration Language (HCL). 8 | 9 | 2. Initialize 10 | 11 | Before Terraform can run it must download its dependencies. 12 | 13 | 3. Plan 14 | 15 | Before Terraform modifies resources it creates an action plan outlining 16 | changes required. 17 | 18 | 4. Apply 19 | 20 | Becuase Terraform uses a declarative model changes are applied rather 21 | than executed. 22 | 23 | Try it: 24 | 25 | Create a file named `workflow.tf` in this directory and add this text: 26 | 27 | ```bash 28 | resource "aws_s3_bucket" "bucket" {} 29 | ``` 30 | 31 | Now run `terraform plan` 32 | 33 |

34 |

35 | 36 | >Initialization: 37 | > 38 | > Before the Plan and Apply phases you must initialize terraform by running: 39 | > ``` 40 | > terraform init 41 | > ``` 42 | > 43 | > Try it: 44 | > 45 | > Run the above command in this directory. What happens? 46 | > 47 | > Now create an empty file in the same directory named `workflow.tf` and run `terraform init` again. 48 | 49 | Next run `terraform apply` 50 | 51 |

52 |

53 | 54 | 55 | Explore the filesystem: 56 | 57 | What files did Terraform create? 58 |

59 |

60 | Clean up: 61 | 62 | run `terraform destroy` -------------------------------------------------------------------------------- /terraform/02_modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | Modules are collections of Terraform resources grouped together by function. Modules can be thought of as customized resources, but they don't behave exactly as resources do. 3 | 4 | ## When to use modules 5 | 6 | > 🚧 Warning: Opinion 7 | >> almost all Terraform code should be contained in modules. Root configs should consist almost entirely of module calls. 8 | 9 | ``` 10 | We do not recommend writing modules that are just thin wrappers around single other resource types. If you have trouble finding a name for your module that isn't the same as the main resource type inside it, that may be a sign that your module is not creating any new abstraction and so the module is adding unnecessary complexity. Just use the resource type directly in the calling module instead. 11 | - Hashicorp 12 | ``` 13 | 14 | ``` 15 | Large modules considered harmful. 16 | - Yevgeniy Brikman(Gruntwork) 17 | ``` 18 | 19 | Counter argument: 20 | 21 | Thin wrappers around resources have a few advantages (CDK uses L1 & L2 Constructs to describe these): 22 | 1. A module may be created to wrap a low level resource in order to provide compliant defaults. Users of the module can be confident that if they don't know what a setting does it will do the right thing by default. 23 | 2. Sometimes the name rule just doesn't apply... Functioning EKS Clusters, VPCs, even S3 Buckets are much more complex than their provider resources. 24 | 3. Layering modules(composition) can ease maintenance. -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "4.44.0" 6 | constraints = "~> 4.0" 7 | hashes = [ 8 | "h1:fwOG8IE3AiHdUBo841jC2p4rIF45pWYettgBshcMyJI=", 9 | "zh:08da139140530900ebb07baedd9044b5002f0296f5f160d96783e72080158326", 10 | "zh:2d677b9e4f195481098cec843d0138f3a198f1f93be42c1d1654b71438e2f5ab", 11 | "zh:3cdf4e06b9b8f30f6652a4519d586febc4ab92168a39df2610f06e04a8e6dda7", 12 | "zh:677933957d1de40c8b5ae252c5cd369617af4cb7a26e8f750ad6f175fef4f767", 13 | "zh:6a266ae5488d6daa53bbe6c2cb8368833381eacd9de7f05f059f5100535a0cb2", 14 | "zh:6cdccaab0a444314b10246c8d58b0ffb84d32ddac70e36a12b45eb518e0ae065", 15 | "zh:6ed49c7680298761416d408bc91cd137ae7cff38181fc143b1dfab1a32b44516", 16 | "zh:8403a0fbf439009b3b0c77969c560cf426aeb3d99a78fdd27afbf4694ca0f3e7", 17 | "zh:8ddfd85c6789bca7c66346da1f3488488c20bc4d773cd26669059c437cfbabd6", 18 | "zh:941455d0fa54b0451387cfd611d63766f4b18cb24038f25ee0794097755e654d", 19 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 20 | "zh:bc01398c714d621ad9f4e81863446cd8e1135a63a5ff7c36d3fb01ab27e96439", 21 | "zh:ce3b39a43b9c5b34a62047fe2a395b72a62cc0b5dc7e8a5be0f778159ac486d2", 22 | "zh:d5891b82511af25570287578318aaee4fa86e05599cc8c81d44ea9e094f4a728", 23 | "zh:df5329c186545d273f9abaeeb39251d6cfcc446bccc44e27d1d7d02ebf145e2f", 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/backend/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "4.44.0" 6 | constraints = "~> 4.0" 7 | hashes = [ 8 | "h1:fwOG8IE3AiHdUBo841jC2p4rIF45pWYettgBshcMyJI=", 9 | "zh:08da139140530900ebb07baedd9044b5002f0296f5f160d96783e72080158326", 10 | "zh:2d677b9e4f195481098cec843d0138f3a198f1f93be42c1d1654b71438e2f5ab", 11 | "zh:3cdf4e06b9b8f30f6652a4519d586febc4ab92168a39df2610f06e04a8e6dda7", 12 | "zh:677933957d1de40c8b5ae252c5cd369617af4cb7a26e8f750ad6f175fef4f767", 13 | "zh:6a266ae5488d6daa53bbe6c2cb8368833381eacd9de7f05f059f5100535a0cb2", 14 | "zh:6cdccaab0a444314b10246c8d58b0ffb84d32ddac70e36a12b45eb518e0ae065", 15 | "zh:6ed49c7680298761416d408bc91cd137ae7cff38181fc143b1dfab1a32b44516", 16 | "zh:8403a0fbf439009b3b0c77969c560cf426aeb3d99a78fdd27afbf4694ca0f3e7", 17 | "zh:8ddfd85c6789bca7c66346da1f3488488c20bc4d773cd26669059c437cfbabd6", 18 | "zh:941455d0fa54b0451387cfd611d63766f4b18cb24038f25ee0794097755e654d", 19 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 20 | "zh:bc01398c714d621ad9f4e81863446cd8e1135a63a5ff7c36d3fb01ab27e96439", 21 | "zh:ce3b39a43b9c5b34a62047fe2a395b72a62cc0b5dc7e8a5be0f778159ac486d2", 22 | "zh:d5891b82511af25570287578318aaee4fa86e05599cc8c81d44ea9e094f4a728", 23 | "zh:df5329c186545d273f9abaeeb39251d6cfcc446bccc44e27d1d7d02ebf145e2f", 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/help: -------------------------------------------------------------------------------- 1 | ======================================================================================================== 2 | 3 | Your cdktf Python project is ready! 4 | 5 | cat help Prints this message 6 | 7 | Compile: 8 | pipenv run ./main.py Compile and run the python code. 9 | 10 | Synthesize: 11 | cdktf synth [stack] Synthesize Terraform resources to cdktf.out/ 12 | 13 | Diff: 14 | cdktf diff [stack] Perform a diff (terraform plan) for the given stack 15 | 16 | Deploy: 17 | cdktf deploy [stack] Deploy the given stack 18 | 19 | Destroy: 20 | cdktf destroy [stack] Destroy the given stack 21 | 22 | Learn more about using modules and providers https://cdk.tf/modules-and-providers 23 | 24 | Use Providers: 25 | 26 | You can add prebuilt providers (if available) or locally generated ones using the add command: 27 | 28 | cdktf provider add "aws@~>3.0" null kreuzwerker/docker 29 | 30 | You can find all prebuilt providers on PyPI: https://pypi.org/user/cdktf-team/ 31 | You can also install these providers directly through pipenv: 32 | 33 | pipenv install cdktf-cdktf-provider-aws 34 | pipenv install cdktf-cdktf-provider-google 35 | pipenv install cdktf-cdktf-provider-azurerm 36 | pipenv install cdktf-cdktf-provider-docker 37 | pipenv install cdktf-cdktf-provider-github 38 | pipenv install cdktf-cdktf-provider-null 39 | 40 | You can also build any module or provider locally. Learn more: https://cdk.tf/modules-and-providers 41 | 42 | ======================================================================================================== -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/help: -------------------------------------------------------------------------------- 1 | ======================================================================================================== 2 | 3 | Your cdktf Python project is ready! 4 | 5 | cat help Prints this message 6 | 7 | Compile: 8 | pipenv run ./main.py Compile and run the python code. 9 | 10 | Synthesize: 11 | cdktf synth [stack] Synthesize Terraform resources to cdktf.out/ 12 | 13 | Diff: 14 | cdktf diff [stack] Perform a diff (terraform plan) for the given stack 15 | 16 | Deploy: 17 | cdktf deploy [stack] Deploy the given stack 18 | 19 | Destroy: 20 | cdktf destroy [stack] Destroy the given stack 21 | 22 | Learn more about using modules and providers https://cdk.tf/modules-and-providers 23 | 24 | Use Providers: 25 | 26 | You can add prebuilt providers (if available) or locally generated ones using the add command: 27 | 28 | cdktf provider add "aws@~>3.0" null kreuzwerker/docker 29 | 30 | You can find all prebuilt providers on PyPI: https://pypi.org/user/cdktf-team/ 31 | You can also install these providers directly through pipenv: 32 | 33 | pipenv install cdktf-cdktf-provider-aws 34 | pipenv install cdktf-cdktf-provider-google 35 | pipenv install cdktf-cdktf-provider-azurerm 36 | pipenv install cdktf-cdktf-provider-docker 37 | pipenv install cdktf-cdktf-provider-github 38 | pipenv install cdktf-cdktf-provider-null 39 | 40 | You can also build any module or provider locally. Learn more: https://cdk.tf/modules-and-providers 41 | 42 | ======================================================================================================== -------------------------------------------------------------------------------- /terraform/01_syntax/06_Dynamic_Blocks/dynamic_blocks.tf: -------------------------------------------------------------------------------- 1 | # Dynamic blocks 2 | # Some configurations can have multiple settings instances. 3 | # These can be written with multiple config blocks with different settings. 4 | resource "aws_s3_bucket" "bucket_static_blocks" { 5 | lifecycle_rule { 6 | prefix = "/path1" 7 | enabled = true 8 | 9 | noncurrent_version_expiration { 10 | days = 90 11 | } 12 | } 13 | lifecycle_rule { 14 | prefix = "/path2" 15 | enabled = true 16 | noncurrent_version_expiration { 17 | days = 90 18 | } 19 | } 20 | } 21 | 22 | 23 | # This gets tedious and error prone when many similar blocks are required. 24 | # Not DRY. 25 | resource "aws_s3_bucket" "bucket_more_static_blocks" { 26 | lifecycle_rule { 27 | prefix = "/path1" 28 | enabled = true 29 | 30 | noncurrent_version_expiration { 31 | days = 90 32 | } 33 | } 34 | lifecycle_rule { 35 | prefix = "/path2" 36 | enabled = true 37 | noncurrent_version_expiration { 38 | days = 90 39 | } 40 | } 41 | lifecycle_rule { 42 | prefix = "/path3" 43 | enabled = true 44 | noncurrent_version_expiration { 45 | days = 90 46 | } 47 | } 48 | lifecycle_rule { 49 | prefix = "/pathN" 50 | enabled = true 51 | noncurrent_version_expiration { 52 | days = 90 53 | } 54 | } 55 | } 56 | 57 | # Dynamic blocks allow us to generate configuration from expressions. 58 | resource "aws_s3_bucket" "bucket_dynamic_blocks" { 59 | dynamic "lifecycle_rule" { 60 | for_each = range(100) 61 | content { 62 | prefix = "/path-${lifecycle_rule.key}" 63 | enabled = true 64 | noncurrent_version_expiration { 65 | days = 90 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /terraform/01_syntax/03_Input_and_Output/io.tf: -------------------------------------------------------------------------------- 1 | #Variables 2 | # Variables are inputs to Terraform. 3 | # Limitations: 4 | # * Immmutable. 5 | # * Don't support expressions. 6 | # Can be specified on the command line with -var bucket_name=my-bucket 7 | # or in files: terraform.tfvars or *.auto.tfvars 8 | # or in environment variables: TF_VAR_bucket_name 9 | variable "bucket_name" { 10 | # `type` is an optional data type specification 11 | type = string 12 | 13 | # `default` is the optional default value. If `default` is ommited 14 | # then a value must be supplied. 15 | default = "bananalab-my-bucket" 16 | } 17 | 18 | # Locals can be used to apply expressions to variables: 19 | locals { 20 | bucket_name_local = "my-company-${var.bucket_name}" 21 | } 22 | 23 | # Variables can also have complex types 24 | variable "complex" { 25 | type = object({ 26 | name = string 27 | aliases = optional(list(string), []) 28 | }) 29 | description = <<-EOT 30 | A complex variable. 31 | EOT 32 | default = { 33 | name = "complex" 34 | } 35 | } 36 | 37 | # Validations 38 | # Limitations: 39 | # * Validations can only refer to their own values. 40 | variable "valid_bucket_name" { 41 | type = string 42 | default = "bananalab-my-bucket" 43 | validation { 44 | condition = can(regex("^bananalab-", var.valid_bucket_name)) 45 | error_message = "Must begin with `bananalab-`." 46 | } 47 | # Multiple validation blocks are allowed. 48 | validation { 49 | condition = length(var.valid_bucket_name) < 64 50 | error_message = "Must be less than 64 characters. Got ${length(var.valid_bucket_name)}" 51 | } 52 | } 53 | 54 | #Outputs 55 | # Outputs are printed by the CLI after `apply`. 56 | # These can reveal calculated values. 57 | # Also used in more advanced use cases: modules, remote_state data source 58 | # Outputs can be retrieved at any time by running `terraform output` 59 | output "bucket_info" { 60 | value = local.bucket_name_local 61 | } 62 | 63 | output "complex" { 64 | value = var.complex 65 | } 66 | -------------------------------------------------------------------------------- /terraform/04_extras/03_collaboration/03_remote_state/remote_state.md: -------------------------------------------------------------------------------- 1 | # Backend for state 2 | 3 | The Terraform state file is critical for managing infrastructure. The state represents the state of the infrastructure as Terraform understands it. If the state file is lost it can be complicated to recover (although it doesn't cause an immediate service impact). 4 | 5 | Its also very important that multiple users have access to the state file if they will collaborate with Terraform. To enable this workflow Terraform supports remote backends. Remote backends are cloud storage providers such as AWS s3. There are many other backend providers but we'll focus on s3 in this excersize. 6 | 7 | ## Configure your account for remote state. 8 | 9 | 1. Begin with the `terraform-live` repo you created previously. 10 | 11 | 2. Add this code to the `versions.tf` file: 12 | 13 | ``` 14 | terraform { 15 | backend "s3" { 16 | bucket = 17 | key = "terraform.tfstate" 18 | } 19 | } 20 | ``` 21 | 3. Run `terraform init`. (There will be a warning about migrating state). 22 | 4. Use the aws console to inspect the s3 bucket. Verify that a state file exists there. 23 | 24 | # State locking and S3. 25 | 26 | It's very important that simultaneous writes do not happen to the state file. Terraform enforces this with locks. Most providers support native locking, but s3 does not. To support locking add a dynamoDB table: 27 | 28 | 1. In the `main.tf` file, create a DynamoDB table: 29 | 30 | ```bash 31 | resource "aws_dynamodb_table" "terraform_lock" { 32 | name = "terraform-lock-table" 33 | read_capacity = 5 34 | write_capacity = 5 35 | hash_key = "LockID" 36 | attribute { 37 | name = "LockID" 38 | type = "S" 39 | } 40 | tags = { 41 | "Name" = "State Lock Table" 42 | } 43 | } 44 | 2. Modify the `terraform/backend` block: 45 | ``` 46 | terraform { 47 | backend "s3" { 48 | bucket = 49 | key = "terraform.tfstate" 50 | dynamodb_table = 51 | } 52 | } 53 | ``` 54 | 2. Run `terraform-init` 55 | 3. In the AWS console inspect the DynamoDB table. What do you see? -------------------------------------------------------------------------------- /terraform/01_syntax/04_Data_Sources_and_Resources/resources.tf: -------------------------------------------------------------------------------- 1 | #Resources 2 | # Objects managed by Terraform such as VMs or S3 Buckets. 3 | # Declaring a Resource tells Terraform that it should CREATE 4 | # and manage the resource described. If the Resource already exists 5 | # it must be imported into Terraform's state. 6 | resource "aws_s3_bucket" "simple_bucket" { 7 | } 8 | 9 | # Resources accept configuration in the form of attributes. 10 | # Each resource supports a unique set of attributes. To understand a resource 11 | # use the reference in the Terraform Registry: 12 | # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket 13 | 14 | # Terraform will return an object representing the resource including all the 15 | # calculated values. 16 | 17 | output "simple_bucket" { 18 | value = aws_s3_bucket.simple_bucket 19 | } 20 | 21 | #Data Sources 22 | # Same as resources except they are not managed by Terraform. 23 | data "aws_caller_identity" "current" {} 24 | 25 | data "aws_availability_zones" "available" { 26 | state = "available" 27 | } 28 | 29 | #Dependency 30 | # Resources can depend on one another. Terraform will ensure that all 31 | # dependencies are met before creating the resource. Dependency can 32 | # be implicit or explicit. 33 | resource "aws_s3_bucket" "dependent_bucket" { 34 | bucket = "${data.aws_caller_identity.current.account_id}-dependent-bucket" 35 | tags = { 36 | # Implicit dependency 37 | dependency = aws_s3_bucket.simple_bucket.arn 38 | } 39 | } 40 | 41 | resource "aws_s3_bucket" "chained_dependent_bucket" { 42 | bucket = "${data.aws_caller_identity.current.account_id}-chained-dependent-bucket" 43 | # Explicit 44 | depends_on = [ 45 | aws_s3_bucket.dependent_bucket 46 | ] 47 | } 48 | # Terraform creates a dependency graph when evaluating it's execution plan. 49 | # The graph can be visualized by running `terraform graph | dot -Tsvg > graph.svg` 50 | # The dependency graph is a Directed Acyclical Graph (DAG) and can't 51 | # contain loops (cycles). 52 | # The graph is processed in parallel when possible. The default concurrency is 10, 53 | # but it can be changed. 54 | 55 | resource "aws_s3_bucket" "independent_bucket" { 56 | bucket = "${data.aws_caller_identity.current.account_id}-independent-bucket" 57 | } 58 | -------------------------------------------------------------------------------- /terraform/01_syntax/00_HCL/README.md: -------------------------------------------------------------------------------- 1 | # HCL 2 | 3 | HCL is a toolkit for creating structured configuration languages that are both 4 | human- and machine-friendly, for use with command-line tools. Although intended 5 | to be generally useful, it is primarily targeted towards devops tools, servers, 6 | etc. 7 | 8 | 9 | ## Why not YAML or JSON? 10 |
11 | JSON 12 | 13 | ```json 14 | { 15 | "io_mode": "async", 16 | "service": { 17 | "http": { 18 | "web_proxy": { 19 | "listen_addr": "127.0.0.1:8080", 20 | "process": { 21 | "main": { 22 | "command": [ 23 | "/usr/local/bin/awesome-app", 24 | "server" 25 | ] 26 | }, 27 | "mgmt": { 28 | "command": [ 29 | "/usr/local/bin/awesome-app", 30 | "mgmt" 31 | ] 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | * High verbosity. 41 | * Prone to brace matching errors. 42 | * Serialization format. 43 | 44 |
45 | 46 |
47 | YAML 48 | 49 | ```yaml 50 | --- 51 | io_mode: async 52 | service: 53 | http: 54 | web_proxy: 55 | listen_addr: 127.0.0.1:8080 56 | process: 57 | main: 58 | command: 59 | - "/usr/local/bin/awesome-app" 60 | - server 61 | mgmt: 62 | command: 63 | - "/usr/local/bin/awesome-app" 64 | - mgmt 65 | ``` 66 | 67 | * Medium verbosity. 68 | * Prone to whitespace errors. 69 | * Serialization format. 70 | 71 |
72 | 73 |
74 | HCL 75 | 76 | ```hcl 77 | io_mode = "async" 78 | 79 | service "http" "web_proxy" { 80 | listen_addr = "127.0.0.1:8080" 81 | 82 | process "main" { 83 | command = ["/usr/local/bin/awesome-app", "server"] 84 | } 85 | 86 | process "mgmt" { 87 | command = ["/usr/local/bin/awesome-app", "mgmt"] 88 | } 89 | } 90 | ``` 91 | 92 | * Medium verbosity 93 | * Balances machine and human readability. 94 | * Also has a JSON syntax. 95 | 96 |
-------------------------------------------------------------------------------- /terraform/01_syntax/02_Configuration/configuration.tf: -------------------------------------------------------------------------------- 1 | #Terraform type 2 | 3 | # Configuration for the Terraform Engine. 4 | 5 | terraform { 6 | required_version = "~>1.2.0" 7 | required_providers { 8 | aws = { 9 | version = "~> 4.0" 10 | source = "hashicorp/aws" 11 | } 12 | } 13 | # experiments = [example] 14 | #backend "remote" { 15 | # organization = "example_corp" 16 | # 17 | # workspaces { 18 | # name = "my-app-prod" 19 | # } 20 | #} 21 | #provider_meta "my-provider" { 22 | # hello = "world" 23 | #} 24 | } 25 | 26 | #Provider type 27 | # Implement cloud specific API and Terraform API. 28 | # Provider configuation is specific to each provider. 29 | # Providers expose Data Sources and Resources to Terraform. 30 | provider "aws" { 31 | region = "us-east-1" 32 | alias = "aws-us-east-1" 33 | #access_key = "my-access-key" 34 | #secret_key = "my-secret-key" 35 | 36 | # Providers may have aliases. Aliases are used when multiple instances 37 | # of a provider are required. 38 | # Example: Resources span multiple regions. 39 | # 40 | # Limitations: 41 | # * All instances of a provider must use the same version. 42 | # * Providers aliases do not support expressions. 43 | # 44 | # Security Alert: Terraform configurations are normally shared via VCS 45 | # so secrets such as passwords and API keys should never 46 | # be stored in the configuration directly. 47 | # Many providers also accept partial configuration via 48 | # environment variables or config files. The AWS provider 49 | # will read the standard AWS CLI settings if they are present 50 | } 51 | 52 | # Version Constraint Syntax: 53 | # * = (or no operator): Allows only one exact version number. Cannot be 54 | # combined with other conditions. 55 | # * !=: Excludes an exact version number. 56 | # * >, >=, <, <=: Comparisons against a specified version, allowing versions 57 | # for which the comparison is true. "Greater-than" requests 58 | # newer versions, and "less-than" requests older versions. 59 | # * ~>: Allows only the rightmost version component to increment. For example, 60 | # to allow new patch releases within a specific minor release, use the full 61 | # version number: ~> 1.0.4 will allow installation of 1.0.5 and 1.0.10 but 62 | # not 1.1.0. This is usually called the pessimistic constraint operator. 63 | -------------------------------------------------------------------------------- /terraform/01_syntax/07_Lifecycle/lifecycle.tf: -------------------------------------------------------------------------------- 1 | # Lifecycle blocks 2 | # Meta-arguments 3 | # * Are available on all resources. 4 | 5 | resource "aws_s3_bucket" "bucket_lifecycle" { 6 | lifecycle { 7 | create_before_destroy = false 8 | # By default Terraform will destroy a resource before creating a new one 9 | # when the resource must be replaced. 10 | prevent_destroy = false 11 | # A guardrail against inadvertant deletion. Some cloud resources also have 12 | # a similar option built in: 13 | # Example: 14 | # aws_rds_cluster.deletion_protection 15 | # The Terraform option only prevents Terraform from deleting it, but the 16 | # resource attribute also protects from API and console deletion. 17 | ignore_changes = [] 18 | # Sometimes resources are changed outside of Terraform under normal 19 | # circumstances. Try to avoid it if possible, but if it's not possible 20 | # add those attributes to this list. 21 | replace_triggered_by = [] 22 | # Terraform providers can specify changes that will reuire a resource to be 23 | # replaced with `ForceNew true`, but sometimes recreation depends on 24 | # relation to other resources. 25 | # Example: 26 | # Autoscaling target must be replaced when ECS service ID changes. 27 | precondition { 28 | # Precondition can validate any expression unless it causes a cycle. 29 | # This is a more robust way to validate inputs becuase it can use 30 | # more complex logic. 31 | condition = !(var.bucket_name != null && var.bucket_prefix != null) 32 | error_message = "Only one of bucket_name or bucket_prefix may be specified." 33 | } 34 | postcondition { 35 | # Post condition can refer to itself. 36 | # Warning: 37 | # Post conditions will create the resource prior to evaluation. 38 | # If the check fails you won't be able to run the plan and fix the 39 | # problem. 40 | condition = self.request_payer == "BucketOwner" 41 | error_message = "request_payer must be BucketOwner." 42 | } 43 | } 44 | } 45 | 46 | # Terraform documentation states: 47 | 48 | # Literal Values Only 49 | # The lifecycle settings all affect how Terraform constructs and traverses 50 | # the dependency graph. As a result, only literal values can be used because 51 | # the processing happens too early for arbitrary expression evaluation. 52 | 53 | # This is incorrect and only applies to: 54 | # * create_before_destroy 55 | # * prevent_destroy 56 | # * ignore_changes 57 | # * replace_triggered_by 58 | -------------------------------------------------------------------------------- /terraform/01_syntax/syntax.md: -------------------------------------------------------------------------------- 1 | # Dynamic blocks 2 | 3 | In this project you will create dynamically set up blocks. 4 | 5 | Typical code of security group looks like the example below: 6 | 7 | ```tf 8 | resource "aws_security_group" "main" { 9 | name = "resource_without_dynamic_block" 10 | vpc_id = data.aws_vpc.main.id 11 | 12 | ingress { 13 | description = "ingress_rule_1" 14 | from_port = 443 15 | to_port = 443 16 | protocol = "tcp" 17 | cidr_blocks = ["0.0.0.0/0"] 18 | } 19 | 20 | ingress { 21 | description = "ingress_rule_2" 22 | from_port = 80 23 | to_port = 80 24 | protocol = "tcp" 25 | cidr_blocks = ["0.0.0.0/0"] 26 | } 27 | 28 | tags = { 29 | Name = "AWS security group non-dynamic block" 30 | } 31 | } 32 | ``` 33 | 34 | The **ingress** is a block. It can be repeated multiple times, however it is possible to do it dynamically by using variables that are lists or maps. 35 | 36 | ## Project "security group" 37 | 38 | 1. Create the directory for the project: 39 | 40 | ```bash 41 | mkdir dynamic && cd dynamic 42 | ``` 43 | 44 | 1. Create files for the project: 45 | 46 | * `main.tf` 47 | * `variables.tf` 48 | * `providers.tf` 49 | 50 | 1. Set up AWS provider in the `providers.tf` file. 51 | 1. In the `main.tf`, create a VPC resource called `vpc`. 52 | 1. In `variables.tf`, add a variable for the ports that the security group would allow: 53 | 54 | ```tf 55 | variable "rules" { 56 | type = list 57 | default = [{ 58 | port = 443 59 | description = "Ingress for HTTPS" 60 | cidr = "0.0.0.0/0" 61 | }, 62 | { 63 | port = 80 64 | description = "Ingress for HTTP" 65 | cidr = "0.0.0.0/0" 66 | }] 67 | } 68 | ``` 69 | 70 | 1. In `main.tf`, add a security group resource that dynamically sets up ingress blocks. 71 | 72 | ```tf 73 | resource "aws_security_group" "main" { 74 | name = "resource_with_dynamic_block" 75 | vpc_id = aws_vpc.vpc.id 76 | 77 | dynamic "ingress" { 78 | for_each = toset(var.rules) 79 | 80 | content { 81 | description = ingress.value.description 82 | from_port = ingress.value.port 83 | to_port = ingress.value.port 84 | protocol = "tcp" 85 | cidr_blocks = [ingress.value.cidr] 86 | } 87 | } 88 | 89 | tags = { 90 | Name = "AWS security group dynamic block" 91 | } 92 | } 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "this" { 2 | bucket = var.bucket 3 | bucket_prefix = var.bucket_prefix 4 | force_destroy = var.force_destroy 5 | object_lock_enabled = var.object_lock_enabled 6 | tags = var.tags 7 | lifecycle { 8 | precondition { 9 | condition = !(var.bucket != null && var.bucket_prefix != null) 10 | error_message = "bucket and bucket_prefix cannot both be set." 11 | } 12 | } 13 | } 14 | 15 | resource "aws_s3_bucket_public_access_block" "this" { 16 | bucket = aws_s3_bucket.this.id 17 | block_public_acls = var.block_public_acls 18 | block_public_policy = var.block_public_policy 19 | ignore_public_acls = var.ignore_public_acls 20 | restrict_public_buckets = var.restrict_public_buckets 21 | } 22 | 23 | resource "aws_s3_bucket_versioning" "this" { 24 | bucket = aws_s3_bucket.this.id 25 | expected_bucket_owner = var.expected_bucket_owner 26 | versioning_configuration { 27 | status = var.versioning_enabled ? "Enabled" : "Suspended" 28 | } 29 | } 30 | 31 | resource "aws_s3_bucket_logging" "this" { 32 | count = var.logging_enabled ? 1 : 0 33 | bucket = aws_s3_bucket.this.id 34 | expected_bucket_owner = var.expected_bucket_owner 35 | target_bucket = var.logging_target_bucket 36 | target_prefix = var.logging_target_prefix 37 | lifecycle { 38 | precondition { 39 | condition = var.logging_target_bucket != null 40 | error_message = "If logging is enabled logging_target_bucket must be set." 41 | } 42 | precondition { 43 | condition = var.logging_target_prefix != null 44 | error_message = "If logging is enabled logging_target_prefix must be set." 45 | } 46 | } 47 | } 48 | 49 | resource "aws_s3_bucket_server_side_encryption_configuration" "this" { 50 | count = var.enable_server_side_encryption ? 1 : 0 51 | bucket = aws_s3_bucket.this.id 52 | expected_bucket_owner = var.expected_bucket_owner 53 | rule { 54 | bucket_key_enabled = true 55 | apply_server_side_encryption_by_default { 56 | sse_algorithm = "aws:kms" 57 | kms_master_key_id = var.kms_master_key_id 58 | } 59 | } 60 | } 61 | 62 | resource "aws_s3_bucket_replication_configuration" "this" { 63 | count = var.enable_replication ? 1 : 0 64 | bucket = aws_s3_bucket.this.id 65 | role = var.replication_role 66 | token = var.replication_token 67 | rule { 68 | status = "Enabled" 69 | destination { 70 | bucket = var.replication_target_bucket 71 | storage_class = "STANDARD" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/examples/simple/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Examples should illustrate typical use cases. 3 | * For multiple examples each should have its own directory. 4 | * 5 | * > Running module examples uses a local state file. 6 | * > If you delete the .terraform directory the resources 7 | * > will be orphaned. 8 | */ 9 | 10 | variable "bucket" { default = null } 11 | variable "bucket_prefix" { default = null } 12 | variable "force_destroy" { default = false } 13 | variable "object_lock_enabled" { default = false } 14 | variable "tags" { default = null } 15 | variable "block_public_acls" { default = true } 16 | variable "block_public_policy" { default = true } 17 | variable "ignore_public_acls" { default = true } 18 | variable "restrict_public_buckets" { default = true } 19 | variable "versioning_enabled" { default = true } 20 | variable "expected_bucket_owner" { default = null } 21 | variable "logging_enabled" { default = false } 22 | variable "logging_target_bucket" { default = null } 23 | variable "logging_target_prefix" { default = null } 24 | variable "logging_target_grant" { default = null } 25 | variable "enable_server_side_encryption" { default = true } 26 | variable "kms_master_key_id" { default = null } 27 | variable "enable_replication" { default = false } 28 | variable "replication_role" { default = null } 29 | variable "replication_target_bucket" { default = null } 30 | 31 | 32 | 33 | module "this" { 34 | source = "../../" 35 | bucket = var.bucket 36 | bucket_prefix = var.bucket_prefix 37 | force_destroy = var.force_destroy 38 | object_lock_enabled = var.object_lock_enabled 39 | tags = var.tags 40 | block_public_acls = var.block_public_acls 41 | block_public_policy = var.block_public_policy 42 | ignore_public_acls = var.ignore_public_acls 43 | versioning_enabled = var.versioning_enabled 44 | logging_enabled = var.logging_enabled 45 | logging_target_bucket = var.logging_target_bucket 46 | logging_target_prefix = var.logging_target_prefix 47 | logging_target_grant = var.logging_target_grant 48 | expected_bucket_owner = var.expected_bucket_owner 49 | restrict_public_buckets = var.restrict_public_buckets 50 | enable_server_side_encryption = var.enable_server_side_encryption 51 | kms_master_key_id = var.kms_master_key_id 52 | enable_replication = var.enable_replication 53 | replication_role = var.replication_role 54 | replication_target_bucket = var.replication_target_bucket 55 | } 56 | 57 | output "result" { 58 | description = <<-EOT 59 | The result of the module. 60 | EOT 61 | value = module.this.result 62 | } 63 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/alb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "app" { 2 | name = "allow_app_ports" 3 | description = "Allow app ports" 4 | vpc_id = module.vpc.result.vpc.id 5 | 6 | ingress { 7 | description = "HTTP from public" 8 | from_port = 80 9 | to_port = 80 10 | protocol = "tcp" 11 | cidr_blocks = ["0.0.0.0/0"] 12 | ipv6_cidr_blocks = ["::/0"] 13 | } 14 | 15 | ingress { 16 | description = "HTTPS from public" 17 | from_port = 443 18 | to_port = 443 19 | protocol = "tcp" 20 | cidr_blocks = ["0.0.0.0/0"] 21 | ipv6_cidr_blocks = ["::/0"] 22 | } 23 | 24 | ingress { 25 | description = "Backend from public" 26 | from_port = 8080 27 | to_port = 8080 28 | protocol = "tcp" 29 | cidr_blocks = ["0.0.0.0/0"] 30 | ipv6_cidr_blocks = ["::/0"] 31 | } 32 | 33 | egress { 34 | from_port = 0 35 | to_port = 0 36 | protocol = "-1" 37 | cidr_blocks = ["0.0.0.0/0"] 38 | ipv6_cidr_blocks = ["::/0"] 39 | } 40 | } 41 | 42 | resource "aws_lb" "this" { 43 | name = "app" 44 | internal = false 45 | load_balancer_type = "application" 46 | security_groups = [aws_security_group.app.id] 47 | subnets = [for subnet in module.vpc.result.public_subnets : subnet.id] 48 | 49 | enable_deletion_protection = false 50 | } 51 | 52 | resource "aws_alb_target_group" "frontend" { 53 | name = "frontend" 54 | port = 80 55 | protocol = "HTTP" 56 | vpc_id = module.vpc.result.vpc.id 57 | target_type = "ip" 58 | 59 | health_check { 60 | healthy_threshold = "3" 61 | interval = "30" 62 | protocol = "HTTP" 63 | matcher = "200" 64 | timeout = "3" 65 | path = "/" 66 | unhealthy_threshold = "2" 67 | } 68 | } 69 | 70 | resource "aws_lb_listener" "frontend" { 71 | load_balancer_arn = aws_lb.this.arn 72 | port = "80" 73 | protocol = "HTTP" 74 | 75 | default_action { 76 | type = "redirect" 77 | 78 | redirect { 79 | port = "443" 80 | protocol = "HTTPS" 81 | status_code = "HTTP_301" 82 | } 83 | } 84 | } 85 | 86 | resource "aws_alb_listener" "frontend_https" { 87 | load_balancer_arn = aws_lb.this.id 88 | port = 443 89 | protocol = "HTTPS" 90 | certificate_arn = aws_acm_certificate_validation.this.certificate_arn 91 | 92 | default_action { 93 | type = "forward" 94 | forward { 95 | target_group { 96 | arn = aws_alb_target_group.frontend.id 97 | } 98 | stickiness { 99 | enabled = true 100 | duration = 3600 101 | } 102 | } 103 | } 104 | } 105 | 106 | resource "aws_route53_record" "learn_terraform" { 107 | zone_id = data.aws_route53_zone.bananalab.zone_id 108 | name = local.app_host 109 | type = "A" 110 | 111 | alias { 112 | name = aws_lb.this.dns_name 113 | zone_id = aws_lb.this.zone_id 114 | evaluate_target_health = true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /terraform/03_example/infrastructure/aws/ecs.tf: -------------------------------------------------------------------------------- 1 | # Define ECS resources. 2 | 3 | resource "aws_ecs_cluster" "this" { 4 | name = "app" 5 | } 6 | 7 | resource "aws_ecs_task_definition" "app" { 8 | family = "app" 9 | network_mode = "awsvpc" 10 | requires_compatibilities = ["FARGATE"] 11 | cpu = 256 12 | memory = 512 13 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn 14 | container_definitions = jsonencode( 15 | [ 16 | { 17 | name = "frontend" 18 | image = "nginx" 19 | essential = true 20 | portMappings = [{ 21 | protocol = "tcp" 22 | containerPort = 80 23 | hostPort = 80 24 | }] 25 | } 26 | ] 27 | ) 28 | } 29 | 30 | resource "aws_iam_role" "ecs_task_execution_role" { 31 | name = "app-ecsTaskExecutionRole" 32 | 33 | assume_role_policy = <<-EOF 34 | { 35 | "Version": "2012-10-17", 36 | "Statement": [ 37 | { 38 | "Action": "sts:AssumeRole", 39 | "Principal": { 40 | "Service": "ecs-tasks.amazonaws.com" 41 | }, 42 | "Effect": "Allow", 43 | "Sid": "" 44 | } 45 | ] 46 | } 47 | EOF 48 | } 49 | 50 | resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-policy-attachment" { 51 | role = aws_iam_role.ecs_task_execution_role.name 52 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 53 | } 54 | 55 | resource "aws_security_group" "service" { 56 | name = "allow_lb_ports" 57 | description = "Allow LB ports" 58 | vpc_id = module.vpc.result.vpc.id 59 | 60 | ingress { 61 | description = "Frontend from LB" 62 | from_port = 80 63 | to_port = 80 64 | protocol = "tcp" 65 | security_groups = [aws_security_group.app.id] 66 | } 67 | 68 | ingress { 69 | description = "Backend from public" 70 | from_port = 8080 71 | to_port = 8080 72 | protocol = "tcp" 73 | security_groups = [aws_security_group.app.id] 74 | } 75 | 76 | egress { 77 | from_port = 0 78 | to_port = 0 79 | protocol = "-1" 80 | cidr_blocks = ["0.0.0.0/0"] 81 | ipv6_cidr_blocks = ["::/0"] 82 | } 83 | } 84 | 85 | resource "aws_ecs_service" "this" { 86 | name = "app" 87 | cluster = aws_ecs_cluster.this.id 88 | task_definition = aws_ecs_task_definition.app.arn 89 | desired_count = 1 90 | deployment_minimum_healthy_percent = 50 91 | deployment_maximum_percent = 200 92 | launch_type = "FARGATE" 93 | health_check_grace_period_seconds = 120 94 | scheduling_strategy = "REPLICA" 95 | 96 | network_configuration { 97 | security_groups = [aws_security_group.service.id] 98 | subnets = [for subnet in module.vpc.result.private_subnets : subnet.id] 99 | assign_public_ip = false 100 | } 101 | 102 | load_balancer { 103 | target_group_arn = aws_alb_target_group.frontend.arn 104 | container_name = "frontend" 105 | container_port = 80 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /terraform/00_hello_terraform/00_setup/setup.md: -------------------------------------------------------------------------------- 1 | # Set up your development environment. 2 | 3 | ## Code editor 4 | Like most development environments Terraform is completely text based. Therefore you will need a text editor. In this class we'll be using Microsoft VSCode. 5 | 6 | Install: 7 |
8 | Windows 9 | https://code.visualstudio.com/docs/setup/windows 10 |
11 |
12 | MacOS 13 | https://code.visualstudio.com/docs/setup/mac 14 |
15 |
16 | Linux 17 | https://code.visualstudio.com/docs/setup/linux 18 |
19 | 20 | Plugins can be installed from the VSCode Marketplace, or from within VSCode itself. 21 | 22 | - [Hashicorp HCL](https://marketplace.visualstudio.com/items?itemName=HashiCorp.HCL) 23 | - [Hashicorp Terraform](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform) 24 | 25 | > 📘 There are other plugins available for Terraform development, but these are the official Hashicorp versions. 26 | 27 | ## Other tools. 28 | There is an ecosystem of tools to help develop Terraform code. We'll be using a few of these, but they're all worth checking out. 29 | 30 | | [ checkov ]( https://checkov.io ) | IaC policy checker | 31 | |-------------------------------------------------------------------------------------------------|-----------------------------------------------| 32 | | [ cloud-nuke ]( https://github.com/gruntwork-io/cloud-nuke ) | Destroyer of clouds. | 33 | | [ graphviz ]( https://graphviz.org/ ) | Graph visualizer | 34 | | [ gnu-make ]( https://www.gnu.org/software/make/ ) | Makefile processor | 35 | | [ pre-commit ]( https://pre-commit.com/ ) | Pre-commit static analysis | 36 | | [ terraform-docs ]( https://terraform-docs.io/ ) | Automatic documentation for Terraform modules | 37 | | [ tfenv ]( https://github.com/tfutils/tfenv ) | Terraform version manager | 38 | | [ tflint ]( https://github.com/terraform-linters/tflint ) | Terraform linter | 39 | | [ m1-terraform-provider-helper ]( https://github.com/kreuzwerker/m1-terraform-provider-helper ) | For m1 Mac compat | 40 | 41 | > 📘 These can be installed on systems that support [Homebrew](https://brew.sh/) (MacOS, Linux, WSL). 42 | 43 | ## Install Terraform 44 | Even though you have installed a lot of tools, Terraform itself is not installed yet. 45 | To install it run: 46 | 47 | `tfenv install latest` 48 | 49 | > 🚧 tfenv may not work on Windows. Instead you can download terraform binaries here: https://www.terraform.io/downloads 50 | 51 | Now that Terraform is installed you can begin exploring it. 52 | 53 | Try `terraform help` to see what commands are available. 54 | 55 | Run `terraform apply` What happens? 56 | > 📘 We'll be exploring a lot of these in detail throughout the course, but feel free to ask if any commands pique your interest. 57 | 58 | > 📘 typing `terraform` at the prompt gets tedious quickly. Try adding an alias `tf=terraform` using your shell's aliasing mechanism. 59 | 60 | ## Check your AWS settings. 61 | > 📘 A full walk through of setting up AWS accounts is outside the scope of the training, but you should be able to run: 62 | 63 | `aws sts get-caller-identity` 64 | 65 | to verify it's configured as you expect. 66 | 67 | ## Devcontainer 68 | This project uses a [ devcontainer ]( https://code.visualstudio.com/docs/devcontainers/containers ) to provide a consistent experience across all platforms. 69 | If you're using VS Code simple click 'Open in Dev Container' to use an environment with all dependencies pre-installed. 70 | -------------------------------------------------------------------------------- /terraform/01_syntax/01_Expressions/terraform.tf: -------------------------------------------------------------------------------- 1 | # Terraform HCL 2 | data "null_data_source" "example" { 3 | inputs = null 4 | } 5 | # type = data 6 | # label = null_data_source 7 | # label = example 8 | # { inputs = null } = body 9 | 10 | # Expressions 11 | # used to refer to or compute values within a configuration. 12 | 13 | # Local Values 14 | # allow you to assign a name to an expression. 15 | # Locals can make your code more readable by eliminating duplicate code (DRY). 16 | locals { 17 | a_local = "Terraform" 18 | } 19 | # There may be multiple locals blocks, but the namespace is global. 20 | 21 | #Interpolation 22 | # Substitute values in strings. 23 | locals { 24 | another_local = "${local.a_local} is fun." 25 | } 26 | 27 | #Data types 28 | # Terraform supports simple and complex data types 29 | locals { 30 | a_string = "This is a string." 31 | a_number = 3.1415 32 | a_boolean = true 33 | a_list = [ 34 | "element1", 35 | 2, 36 | "three" 37 | ] 38 | a_map = { 39 | key = "value" 40 | } 41 | 42 | # Complex 43 | person = { 44 | name = "Robert Jordan", 45 | phone_numbers = { 46 | home = "415-444-1212", 47 | mobile = "415-555-1313" 48 | }, 49 | active = false, 50 | age = 32 51 | } 52 | } 53 | 54 | #Operators 55 | # Terraform supports arithmetic and logical operations in expressions too 56 | locals { 57 | //Arithmetic 58 | three = 1 + 2 // addition 59 | two = 3 - 1 // subtraction 60 | one = 2 / 2 // division 61 | zero = 1 * 0 // multiplication 62 | 63 | //Logical 64 | t = true || false // OR true if either value is true 65 | f = true && false // AND true if both values are true 66 | 67 | //Comparison 68 | gt = 2 > 1 // true if right value is greater 69 | gte = 2 >= 1 // true if right value is greater or equal 70 | lt = 1 < 2 // true if left value is greater 71 | lte = 1 <= 2 // true if left value is greate or equal 72 | eq = 1 == 1 // true if left and right are equal 73 | neq = 1 != 2 // true if left and right are not equal 74 | } 75 | 76 | # References 77 | locals { 78 | a_ref = data.null_data_source.example.random 79 | } 80 | 81 | #Conditionals 82 | locals { 83 | is_null = data.null_data_source.example.random == null ? true : false 84 | } 85 | 86 | #Functions 87 | # Terraform has 100+ built in functions (but no ability to define custom functions!) 88 | # https://www.terraform.io/docs/configuration/functions.html 89 | # The syntax for a function call is (, ). 90 | locals { 91 | //Date and Time 92 | ts = timestamp() //Returns the current date and time. 93 | current_month = formatdate("MMMM", local.ts) 94 | tomorrow = formatdate("DD", timeadd(local.ts, "24h")) 95 | } 96 | 97 | locals { 98 | //String 99 | lcase = lower("A mixed case String") 100 | ucase = upper("a lower case string") 101 | trimmed = trimspace(" A string with leading and trailing spaces ") 102 | formatted = format("Hello %s", "World") 103 | formatted_list = formatlist("Hello %s", ["John", "Paul", "George", "Ringo"]) 104 | } 105 | 106 | #Iteration 107 | # HCL has a `for` syntax for iterating over list values. 108 | # 109 | locals { 110 | l = ["one", "two", "three"] 111 | upper_list = [for item in local.l : upper(item)] 112 | upper_map = { for item in local.l : item => upper(item) } 113 | } 114 | 115 | #Filtering 116 | # The `for` syntax can also filter with the `if` clause. 117 | locals { 118 | n = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 119 | evens = [for i in local.n : i if i % 2 == 0] 120 | } 121 | 122 | #Directives and Heredocs 123 | # HCL supports more complex string templating that can be used to generate 124 | # full descriptive paragraphs too. 125 | locals { 126 | heredoc = <<-EOT 127 | This is called a `heredoc`. It's a string literal 128 | that can span multiple lines. 129 | EOT 130 | } 131 | 132 | locals { 133 | template = <<-EOT 134 | This is a `heredoc` with directives. 135 | %{if local.person.name == ""} 136 | Sorry, I don't know your name. 137 | %{else} 138 | Hi ${local.person.name} 139 | %{endif} 140 | EOT 141 | } 142 | -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bucket" { 2 | type = string 3 | description = <<-EOT 4 | The name of the S3 bucket to create 5 | EOT 6 | default = null 7 | } 8 | 9 | variable "bucket_prefix" { 10 | type = string 11 | description = <<-EOT 12 | The prefix to use for the S3 bucket name 13 | EOT 14 | default = null 15 | } 16 | 17 | variable "force_destroy" { 18 | type = bool 19 | description = <<-EOT 20 | Force destroy the bucket if it exists 21 | EOT 22 | default = false 23 | } 24 | 25 | variable "object_lock_enabled" { 26 | type = bool 27 | description = <<-EOT 28 | Enable object lock for the bucket 29 | EOT 30 | default = false 31 | } 32 | 33 | variable "tags" { 34 | type = map(any) 35 | description = <<-EOT 36 | Tags to apply to the bucket 37 | EOT 38 | default = null 39 | } 40 | variable "block_public_acls" { 41 | type = bool 42 | description = <<-EOT 43 | Block public access to the bucket 44 | EOT 45 | default = true 46 | } 47 | 48 | variable "block_public_policy" { 49 | type = bool 50 | description = <<-EOT 51 | Block public policy access to the bucket 52 | EOT 53 | default = true 54 | } 55 | 56 | variable "ignore_public_acls" { 57 | type = bool 58 | description = <<-EOT 59 | Ignore public acls for the bucket 60 | EOT 61 | default = true 62 | } 63 | 64 | variable "restrict_public_buckets" { 65 | type = bool 66 | description = <<-EOT 67 | Restrict public access to the bucket 68 | EOT 69 | default = true 70 | } 71 | 72 | variable "versioning_enabled" { 73 | type = bool 74 | description = <<-EOT 75 | Enable versioning for the bucket 76 | EOT 77 | default = true 78 | } 79 | 80 | variable "expected_bucket_owner" { 81 | type = string 82 | description = <<-EOT 83 | The expected owner of the bucket 84 | EOT 85 | default = null 86 | } 87 | 88 | variable "logging_enabled" { 89 | type = bool 90 | description = <<-EOT 91 | Enable logging for the bucket 92 | EOT 93 | default = true 94 | } 95 | 96 | variable "logging_target_bucket" { 97 | type = string 98 | description = <<-EOT 99 | The target bucket for the logging configuration 100 | EOT 101 | default = null 102 | } 103 | variable "logging_target_prefix" { 104 | type = string 105 | description = <<-EOT 106 | The target prefix for the logging configuration 107 | EOT 108 | default = null 109 | } 110 | 111 | variable "logging_target_grant" { 112 | type = list(object({ 113 | grantee = optional(object({ 114 | type = string 115 | id = optional(string) 116 | uri = optional(string) 117 | email_address = optional(string) 118 | })) 119 | permission = optional(string) 120 | })) 121 | description = <<-EOT 122 | The target grant for the logging configuration 123 | EOT 124 | default = null 125 | } 126 | 127 | variable "enable_server_side_encryption" { 128 | type = bool 129 | description = <<-EOT 130 | Enable server side encryption for the bucket 131 | EOT 132 | default = true 133 | } 134 | 135 | variable "kms_master_key_id" { 136 | type = string 137 | description = <<-EOT 138 | The KMS key id to use for encryption 139 | EOT 140 | default = null 141 | } 142 | 143 | variable "enable_replication" { 144 | type = bool 145 | description = <<-EOT 146 | Enable replication for the bucket 147 | EOT 148 | default = true 149 | } 150 | 151 | variable "replication_role" { 152 | type = string 153 | description = <<-EOT 154 | The role ARN to use for replication 155 | EOT 156 | default = null 157 | } 158 | 159 | variable "replication_target_bucket" { 160 | type = string 161 | description = <<-EOT 162 | The target bucket to use for replication 163 | EOT 164 | default = null 165 | } 166 | 167 | variable "replication_token" { 168 | type = string 169 | description = <<-EOT 170 | The token to use for replication 171 | EOT 172 | default = null 173 | } 174 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/aws/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "3b69a790d03cb5bf5f7cc2c87f7d5dd1a0e20aa1ec26091402bb476dd9380994" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "attrs": { 20 | "hashes": [ 21 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", 22 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" 23 | ], 24 | "markers": "python_version >= '3.5'", 25 | "version": "==22.1.0" 26 | }, 27 | "cattrs": { 28 | "hashes": [ 29 | "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6", 30 | "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364" 31 | ], 32 | "markers": "python_version >= '3.7' and python_version < '4.0'", 33 | "version": "==22.1.0" 34 | }, 35 | "cdktf": { 36 | "hashes": [ 37 | "sha256:3e9a57b5d90047f3b9b66cef4171261fb6845d241d0cb1caff4fec73a433dd17", 38 | "sha256:a597979768b71a25a981284309087a4d266a8cae51c501bb7ccd3ec1199f4d92" 39 | ], 40 | "index": "pypi", 41 | "version": "==0.12.2" 42 | }, 43 | "constructs": { 44 | "hashes": [ 45 | "sha256:b5f8a3a44e72177bc37f4edbb8a178ec9c0fba65ea3061dd88527871f43514c0", 46 | "sha256:e8049b0bff2b3e171e6800e8ccd44ab69fa36a0967ee1f0f425c0a8a46e20b86" 47 | ], 48 | "markers": "python_version ~= '3.7'", 49 | "version": "==10.1.103" 50 | }, 51 | "exceptiongroup": { 52 | "hashes": [ 53 | "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337", 54 | "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96" 55 | ], 56 | "markers": "python_version < '3.11'", 57 | "version": "==1.0.0rc9" 58 | }, 59 | "iniconfig": { 60 | "hashes": [ 61 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 62 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 63 | ], 64 | "version": "==1.1.1" 65 | }, 66 | "jsii": { 67 | "hashes": [ 68 | "sha256:a4868f8ae05ff62fef328dd197d5834c0f3c291948a1768ad931f1fc05935cb2", 69 | "sha256:ca16eb9c15377b77d10942439b089a10eb9657bffc63559c7a08bb7141cacc0c" 70 | ], 71 | "markers": "python_version ~= '3.7'", 72 | "version": "==1.67.0" 73 | }, 74 | "packaging": { 75 | "hashes": [ 76 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 77 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 78 | ], 79 | "markers": "python_version >= '3.6'", 80 | "version": "==21.3" 81 | }, 82 | "pluggy": { 83 | "hashes": [ 84 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 85 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 86 | ], 87 | "markers": "python_version >= '3.6'", 88 | "version": "==1.0.0" 89 | }, 90 | "publication": { 91 | "hashes": [ 92 | "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6", 93 | "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4" 94 | ], 95 | "version": "==0.0.3" 96 | }, 97 | "py": { 98 | "hashes": [ 99 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 100 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 101 | ], 102 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 103 | "version": "==1.11.0" 104 | }, 105 | "pyparsing": { 106 | "hashes": [ 107 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 108 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 109 | ], 110 | "markers": "python_full_version >= '3.6.8'", 111 | "version": "==3.0.9" 112 | }, 113 | "pytest": { 114 | "hashes": [ 115 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 116 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 117 | ], 118 | "index": "pypi", 119 | "version": "==7.1.3" 120 | }, 121 | "python-dateutil": { 122 | "hashes": [ 123 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 124 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 125 | ], 126 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 127 | "version": "==2.8.2" 128 | }, 129 | "six": { 130 | "hashes": [ 131 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 132 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 133 | ], 134 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 135 | "version": "==1.16.0" 136 | }, 137 | "tomli": { 138 | "hashes": [ 139 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 140 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 141 | ], 142 | "markers": "python_version >= '3.7'", 143 | "version": "==2.0.1" 144 | }, 145 | "typeguard": { 146 | "hashes": [ 147 | "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", 148 | "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1" 149 | ], 150 | "markers": "python_full_version >= '3.5.3'", 151 | "version": "==2.13.3" 152 | }, 153 | "typing-extensions": { 154 | "hashes": [ 155 | "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", 156 | "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" 157 | ], 158 | "markers": "python_version >= '3.7'", 159 | "version": "==4.3.0" 160 | } 161 | }, 162 | "develop": {} 163 | } 164 | -------------------------------------------------------------------------------- /terraform/04_extras/04_cdktf/docker/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "34ba170d2fd3f38e5d96fde851f61e40e21a580c29678f440522926e193b4a19" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "attrs": { 20 | "hashes": [ 21 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", 22 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" 23 | ], 24 | "markers": "python_version >= '3.5'", 25 | "version": "==22.1.0" 26 | }, 27 | "cattrs": { 28 | "hashes": [ 29 | "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6", 30 | "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364" 31 | ], 32 | "markers": "python_version >= '3.7' and python_version < '4.0'", 33 | "version": "==22.1.0" 34 | }, 35 | "cdktf": { 36 | "hashes": [ 37 | "sha256:3e9a57b5d90047f3b9b66cef4171261fb6845d241d0cb1caff4fec73a433dd17", 38 | "sha256:a597979768b71a25a981284309087a4d266a8cae51c501bb7ccd3ec1199f4d92" 39 | ], 40 | "index": "pypi", 41 | "version": "==0.12.2" 42 | }, 43 | "cdktf-cdktf-provider-docker": { 44 | "hashes": [ 45 | "sha256:7de9fa7eacebc8261265f545b6195c5476f7c7ae437419a7c2401c77d680420c", 46 | "sha256:f27c1eb456edb4422eba69ab03dad095a76219596b28472d32eaddc7bc939095" 47 | ], 48 | "index": "pypi", 49 | "version": "==2.0.58" 50 | }, 51 | "constructs": { 52 | "hashes": [ 53 | "sha256:b5f8a3a44e72177bc37f4edbb8a178ec9c0fba65ea3061dd88527871f43514c0", 54 | "sha256:e8049b0bff2b3e171e6800e8ccd44ab69fa36a0967ee1f0f425c0a8a46e20b86" 55 | ], 56 | "markers": "python_version ~= '3.7'", 57 | "version": "==10.1.103" 58 | }, 59 | "exceptiongroup": { 60 | "hashes": [ 61 | "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337", 62 | "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96" 63 | ], 64 | "markers": "python_version < '3.11'", 65 | "version": "==1.0.0rc9" 66 | }, 67 | "iniconfig": { 68 | "hashes": [ 69 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 70 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 71 | ], 72 | "version": "==1.1.1" 73 | }, 74 | "jsii": { 75 | "hashes": [ 76 | "sha256:a4868f8ae05ff62fef328dd197d5834c0f3c291948a1768ad931f1fc05935cb2", 77 | "sha256:ca16eb9c15377b77d10942439b089a10eb9657bffc63559c7a08bb7141cacc0c" 78 | ], 79 | "markers": "python_version ~= '3.7'", 80 | "version": "==1.67.0" 81 | }, 82 | "packaging": { 83 | "hashes": [ 84 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 85 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 86 | ], 87 | "markers": "python_version >= '3.6'", 88 | "version": "==21.3" 89 | }, 90 | "pluggy": { 91 | "hashes": [ 92 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 93 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 94 | ], 95 | "markers": "python_version >= '3.6'", 96 | "version": "==1.0.0" 97 | }, 98 | "publication": { 99 | "hashes": [ 100 | "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6", 101 | "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4" 102 | ], 103 | "version": "==0.0.3" 104 | }, 105 | "py": { 106 | "hashes": [ 107 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 108 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 109 | ], 110 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 111 | "version": "==1.11.0" 112 | }, 113 | "pyparsing": { 114 | "hashes": [ 115 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 116 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 117 | ], 118 | "markers": "python_full_version >= '3.6.8'", 119 | "version": "==3.0.9" 120 | }, 121 | "pytest": { 122 | "hashes": [ 123 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 124 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 125 | ], 126 | "index": "pypi", 127 | "version": "==7.1.3" 128 | }, 129 | "python-dateutil": { 130 | "hashes": [ 131 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 132 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 133 | ], 134 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 135 | "version": "==2.8.2" 136 | }, 137 | "six": { 138 | "hashes": [ 139 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 140 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 141 | ], 142 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 143 | "version": "==1.16.0" 144 | }, 145 | "tomli": { 146 | "hashes": [ 147 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 148 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 149 | ], 150 | "markers": "python_version >= '3.7'", 151 | "version": "==2.0.1" 152 | }, 153 | "typeguard": { 154 | "hashes": [ 155 | "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", 156 | "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1" 157 | ], 158 | "markers": "python_full_version >= '3.5.3'", 159 | "version": "==2.13.3" 160 | }, 161 | "typing-extensions": { 162 | "hashes": [ 163 | "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", 164 | "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" 165 | ], 166 | "markers": "python_version >= '3.7'", 167 | "version": "==4.3.0" 168 | } 169 | }, 170 | "develop": {} 171 | } 172 | -------------------------------------------------------------------------------- /terraform/02_modules/03_complete/aws-s3-bucket/README.md: -------------------------------------------------------------------------------- 1 | # aws-s3-bucket 2 | 3 | 4 | 5 | 8 | L1 Module to create an S3 bucket. 9 | 10 | ## Example 11 | 12 | ```hcl 13 | /** 14 | * Examples should illustrate typical use cases. 15 | * For multiple examples each should have its own directory. 16 | * 17 | * > Running module examples uses a local state file. 18 | * > If you delete the .terraform directory the resources 19 | * > will be orphaned. 20 | */ 21 | 22 | variable "bucket" { default = null } 23 | variable "bucket_prefix" { default = null } 24 | variable "force_destroy" { default = false } 25 | variable "object_lock_enabled" { default = false } 26 | variable "tags" { default = null } 27 | variable "block_public_acls" { default = true } 28 | variable "block_public_policy" { default = true } 29 | variable "ignore_public_acls" { default = true } 30 | variable "restrict_public_buckets" { default = true } 31 | variable "versioning_enabled" { default = true } 32 | variable "expected_bucket_owner" { default = null } 33 | variable "logging_enabled" { default = true } 34 | variable "logging_target_bucket" { default = null } 35 | variable "logging_target_prefix" { default = null } 36 | variable "logging_target_grant" { default = null } 37 | variable "enable_server_side_encryption" { default = true } 38 | variable "kms_master_key_id" { default = null } 39 | variable "enable_replication" { default = false } 40 | variable "replication_role" { default = null } 41 | variable "replication_target_bucket" { default = null } 42 | 43 | 44 | 45 | module "this" { 46 | source = "../../" 47 | bucket = var.bucket 48 | bucket_prefix = var.bucket_prefix 49 | force_destroy = var.force_destroy 50 | object_lock_enabled = var.object_lock_enabled 51 | tags = var.tags 52 | block_public_acls = var.block_public_acls 53 | block_public_policy = var.block_public_policy 54 | ignore_public_acls = var.ignore_public_acls 55 | versioning_enabled = var.versioning_enabled 56 | logging_enabled = var.logging_enabled 57 | logging_target_bucket = var.logging_target_bucket 58 | logging_target_prefix = var.logging_target_prefix 59 | logging_target_grant = var.logging_target_grant 60 | expected_bucket_owner = var.expected_bucket_owner 61 | restrict_public_buckets = var.restrict_public_buckets 62 | enable_server_side_encryption = var.enable_server_side_encryption 63 | kms_master_key_id = var.kms_master_key_id 64 | enable_replication = var.enable_replication 65 | replication_role = var.replication_role 66 | replication_target_bucket = var.replication_target_bucket 67 | } 68 | 69 | output "result" { 70 | description = <<-EOT 71 | The result of the module. 72 | EOT 73 | value = module.this.result 74 | } 75 | ``` 76 | 77 | 78 | ## Modules 79 | 80 | No modules. 81 | 82 | ## Providers 83 | 84 | | Name | Version | 85 | |------|---------| 86 | | [aws](#provider\_aws) | ~>4 | 87 | 88 | ## Requirements 89 | 90 | | Name | Version | 91 | |------|---------| 92 | | [terraform](#requirement\_terraform) | >= 1.2.0 | 93 | | [aws](#requirement\_aws) | ~>4 | 94 | 95 | ## Resources 96 | 97 | | Name | Type | 98 | |------|------| 99 | | [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | 100 | | [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | 101 | | [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | 102 | | [aws_s3_bucket_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | 103 | | [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | 104 | | [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | 105 | 106 | ## Inputs 107 | 108 | | Name | Description | Type | Default | Required | 109 | |------|-------------|------|---------|:--------:| 110 | | [block\_public\_acls](#input\_block\_public\_acls) | Block public access to the bucket | `bool` | `true` | no | 111 | | [block\_public\_policy](#input\_block\_public\_policy) | Block public policy access to the bucket | `bool` | `true` | no | 112 | | [bucket](#input\_bucket) | The name of the S3 bucket to create | `string` | `null` | no | 113 | | [bucket\_prefix](#input\_bucket\_prefix) | The prefix to use for the S3 bucket name | `string` | `null` | no | 114 | | [enable\_replication](#input\_enable\_replication) | Enable replication for the bucket | `bool` | `true` | no | 115 | | [enable\_server\_side\_encryption](#input\_enable\_server\_side\_encryption) | Enable server side encryption for the bucket | `bool` | `true` | no | 116 | | [expected\_bucket\_owner](#input\_expected\_bucket\_owner) | The expected owner of the bucket | `string` | `null` | no | 117 | | [force\_destroy](#input\_force\_destroy) | Force destroy the bucket if it exists | `bool` | `false` | no | 118 | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Ignore public acls for the bucket | `bool` | `true` | no | 119 | | [kms\_master\_key\_id](#input\_kms\_master\_key\_id) | The KMS key id to use for encryption | `string` | `null` | no | 120 | | [logging\_enabled](#input\_logging\_enabled) | Enable logging for the bucket | `bool` | `true` | no | 121 | | [logging\_target\_bucket](#input\_logging\_target\_bucket) | The target bucket for the logging configuration | `string` | `null` | no | 122 | | [logging\_target\_grant](#input\_logging\_target\_grant) | The target grant for the logging configuration |
list(object({
grantee = optional(object({
type = string
id = optional(string)
uri = optional(string)
email_address = optional(string)
}))
permission = optional(string)
}))
| `null` | no | 123 | | [logging\_target\_prefix](#input\_logging\_target\_prefix) | The target prefix for the logging configuration | `string` | `null` | no | 124 | | [object\_lock\_enabled](#input\_object\_lock\_enabled) | Enable object lock for the bucket | `bool` | `false` | no | 125 | | [replication\_role](#input\_replication\_role) | The role ARN to use for replication | `string` | `null` | no | 126 | | [replication\_target\_bucket](#input\_replication\_target\_bucket) | The target bucket to use for replication | `string` | `null` | no | 127 | | [replication\_token](#input\_replication\_token) | The token to use for replication | `string` | `null` | no | 128 | | [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Restrict public access to the bucket | `bool` | `true` | no | 129 | | [tags](#input\_tags) | Tags to apply to the bucket | `map(any)` | `null` | no | 130 | | [versioning\_enabled](#input\_versioning\_enabled) | Enable versioning for the bucket | `bool` | `true` | no | 131 | 132 | ## Outputs 133 | 134 | | Name | Description | 135 | |------|-------------| 136 | | [result](#output\_result) | The result of the module. | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /terraform/01_syntax/04_Data_Sources_and_Resources/graph.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | [root] aws_s3_bucket.bucket_validated_conditions (orphan) 14 | 15 | aws_s3_bucket.bucket_validated_conditions 16 | 17 | 18 | 19 | [root] provider["registry.terraform.io/hashicorp/aws"] 20 | 21 | provider["registry.terraform.io/hashicorp/aws"] 22 | 23 | 24 | 25 | [root] aws_s3_bucket.bucket_validated_conditions (orphan)->[root] provider["registry.terraform.io/hashicorp/aws"] 26 | 27 | 28 | 29 | 30 | 31 | [root] aws_s3_bucket.chained_dependent_bucket (expand) 32 | 33 | aws_s3_bucket.chained_dependent_bucket 34 | 35 | 36 | 37 | [root] aws_s3_bucket.dependent_bucket (expand) 38 | 39 | aws_s3_bucket.dependent_bucket 40 | 41 | 42 | 43 | [root] aws_s3_bucket.chained_dependent_bucket (expand)->[root] aws_s3_bucket.dependent_bucket (expand) 44 | 45 | 46 | 47 | 48 | 49 | [root] aws_s3_bucket.simple_bucket (expand) 50 | 51 | aws_s3_bucket.simple_bucket 52 | 53 | 54 | 55 | [root] aws_s3_bucket.dependent_bucket (expand)->[root] aws_s3_bucket.simple_bucket (expand) 56 | 57 | 58 | 59 | 60 | 61 | [root] data.aws_caller_identity.current (expand) 62 | 63 | data.aws_caller_identity.current 64 | 65 | 66 | 67 | [root] aws_s3_bucket.dependent_bucket (expand)->[root] data.aws_caller_identity.current (expand) 68 | 69 | 70 | 71 | 72 | 73 | [root] aws_s3_bucket.independent_bucket (expand) 74 | 75 | aws_s3_bucket.independent_bucket 76 | 77 | 78 | 79 | [root] aws_s3_bucket.independent_bucket (expand)->[root] data.aws_caller_identity.current (expand) 80 | 81 | 82 | 83 | 84 | 85 | [root] aws_s3_bucket.simple_bucket (expand)->[root] provider["registry.terraform.io/hashicorp/aws"] 86 | 87 | 88 | 89 | 90 | 91 | [root] data.aws_availability_zones.available (expand) 92 | 93 | data.aws_availability_zones.available 94 | 95 | 96 | 97 | [root] data.aws_availability_zones.available (expand)->[root] provider["registry.terraform.io/hashicorp/aws"] 98 | 99 | 100 | 101 | 102 | 103 | [root] data.aws_caller_identity.current (expand)->[root] provider["registry.terraform.io/hashicorp/aws"] 104 | 105 | 106 | 107 | 108 | 109 | [root] data.null_data_source.example (expand) 110 | 111 | data.null_data_source.example 112 | 113 | 114 | 115 | [root] provider["registry.terraform.io/hashicorp/null"] 116 | 117 | provider["registry.terraform.io/hashicorp/null"] 118 | 119 | 120 | 121 | [root] data.null_data_source.example (expand)->[root] provider["registry.terraform.io/hashicorp/null"] 122 | 123 | 124 | 125 | 126 | 127 | [root] output.aws_caller_info 128 | 129 | 130 | 131 | output.aws_caller_info 132 | 133 | 134 | 135 | [root] output.aws_caller_info->[root] data.aws_caller_identity.current (expand) 136 | 137 | 138 | 139 | 140 | 141 | [root] output.bucket_info 142 | 143 | 144 | 145 | output.bucket_info 146 | 147 | 148 | 149 | [root] output.bucket_info->[root] aws_s3_bucket.simple_bucket (expand) 150 | 151 | 152 | 153 | 154 | 155 | [root] output.directive 156 | 157 | 158 | 159 | output.directive 160 | 161 | 162 | 163 | [root] local.person (expand) 164 | 165 | [root] local.person (expand) 166 | 167 | 168 | 169 | [root] output.directive->[root] local.person (expand) 170 | 171 | 172 | 173 | 174 | 175 | [root] output.heredoc 176 | 177 | 178 | 179 | output.heredoc 180 | 181 | 182 | 183 | [root] local.a_ref (expand) 184 | 185 | [root] local.a_ref (expand) 186 | 187 | 188 | 189 | [root] local.a_ref (expand)->[root] data.null_data_source.example (expand) 190 | 191 | 192 | 193 | 194 | 195 | [root] local.another_local (expand) 196 | 197 | [root] local.another_local (expand) 198 | 199 | 200 | 201 | [root] local.a_local (expand) 202 | 203 | [root] local.a_local (expand) 204 | 205 | 206 | 207 | [root] local.another_local (expand)->[root] local.a_local (expand) 208 | 209 | 210 | 211 | 212 | 213 | [root] local.current_month (expand) 214 | 215 | [root] local.current_month (expand) 216 | 217 | 218 | 219 | [root] local.ts (expand) 220 | 221 | [root] local.ts (expand) 222 | 223 | 224 | 225 | [root] local.current_month (expand)->[root] local.ts (expand) 226 | 227 | 228 | 229 | 230 | 231 | [root] local.evens (expand) 232 | 233 | [root] local.evens (expand) 234 | 235 | 236 | 237 | [root] local.n (expand) 238 | 239 | [root] local.n (expand) 240 | 241 | 242 | 243 | [root] local.evens (expand)->[root] local.n (expand) 244 | 245 | 246 | 247 | 248 | 249 | [root] local.is_null (expand) 250 | 251 | [root] local.is_null (expand) 252 | 253 | 254 | 255 | [root] local.is_null (expand)->[root] data.null_data_source.example (expand) 256 | 257 | 258 | 259 | 260 | 261 | [root] local.tomorrow (expand) 262 | 263 | [root] local.tomorrow (expand) 264 | 265 | 266 | 267 | [root] local.tomorrow (expand)->[root] local.ts (expand) 268 | 269 | 270 | 271 | 272 | 273 | [root] local.upper_list (expand) 274 | 275 | [root] local.upper_list (expand) 276 | 277 | 278 | 279 | [root] local.l (expand) 280 | 281 | [root] local.l (expand) 282 | 283 | 284 | 285 | [root] local.upper_list (expand)->[root] local.l (expand) 286 | 287 | 288 | 289 | 290 | 291 | [root] local.upper_map (expand) 292 | 293 | [root] local.upper_map (expand) 294 | 295 | 296 | 297 | [root] local.upper_map (expand)->[root] local.l (expand) 298 | 299 | 300 | 301 | 302 | 303 | [root] provider["registry.terraform.io/hashicorp/aws"] (close) 304 | 305 | [root] provider["registry.terraform.io/hashicorp/aws"] (close) 306 | 307 | 308 | 309 | [root] provider["registry.terraform.io/hashicorp/aws"] (close)->[root] aws_s3_bucket.bucket_validated_conditions (orphan) 310 | 311 | 312 | 313 | 314 | 315 | [root] provider["registry.terraform.io/hashicorp/aws"] (close)->[root] aws_s3_bucket.chained_dependent_bucket (expand) 316 | 317 | 318 | 319 | 320 | 321 | [root] provider["registry.terraform.io/hashicorp/aws"] (close)->[root] aws_s3_bucket.independent_bucket (expand) 322 | 323 | 324 | 325 | 326 | 327 | [root] provider["registry.terraform.io/hashicorp/aws"] (close)->[root] data.aws_availability_zones.available (expand) 328 | 329 | 330 | 331 | 332 | 333 | [root] provider["registry.terraform.io/hashicorp/null"] (close) 334 | 335 | [root] provider["registry.terraform.io/hashicorp/null"] (close) 336 | 337 | 338 | 339 | [root] provider["registry.terraform.io/hashicorp/null"] (close)->[root] data.null_data_source.example (expand) 340 | 341 | 342 | 343 | 344 | 345 | [root] root 346 | 347 | [root] root 348 | 349 | 350 | 351 | [root] root->[root] output.aws_caller_info 352 | 353 | 354 | 355 | 356 | 357 | [root] root->[root] output.bucket_info 358 | 359 | 360 | 361 | 362 | 363 | [root] root->[root] output.directive 364 | 365 | 366 | 367 | 368 | 369 | [root] root->[root] output.heredoc 370 | 371 | 372 | 373 | 374 | 375 | [root] root->[root] local.a_ref (expand) 376 | 377 | 378 | 379 | 380 | 381 | [root] root->[root] local.another_local (expand) 382 | 383 | 384 | 385 | 386 | 387 | [root] root->[root] local.current_month (expand) 388 | 389 | 390 | 391 | 392 | 393 | [root] root->[root] local.evens (expand) 394 | 395 | 396 | 397 | 398 | 399 | [root] root->[root] local.is_null (expand) 400 | 401 | 402 | 403 | 404 | 405 | [root] root->[root] local.tomorrow (expand) 406 | 407 | 408 | 409 | 410 | 411 | [root] root->[root] local.upper_list (expand) 412 | 413 | 414 | 415 | 416 | 417 | [root] root->[root] local.upper_map (expand) 418 | 419 | 420 | 421 | 422 | 423 | [root] root->[root] provider["registry.terraform.io/hashicorp/aws"] (close) 424 | 425 | 426 | 427 | 428 | 429 | [root] root->[root] provider["registry.terraform.io/hashicorp/null"] (close) 430 | 431 | 432 | 433 | 434 | 435 | [root] local.a_boolean (expand) 436 | 437 | [root] local.a_boolean (expand) 438 | 439 | 440 | 441 | [root] root->[root] local.a_boolean (expand) 442 | 443 | 444 | 445 | 446 | 447 | [root] local.a_list (expand) 448 | 449 | [root] local.a_list (expand) 450 | 451 | 452 | 453 | [root] root->[root] local.a_list (expand) 454 | 455 | 456 | 457 | 458 | 459 | [root] local.a_map (expand) 460 | 461 | [root] local.a_map (expand) 462 | 463 | 464 | 465 | [root] root->[root] local.a_map (expand) 466 | 467 | 468 | 469 | 470 | 471 | [root] local.a_number (expand) 472 | 473 | [root] local.a_number (expand) 474 | 475 | 476 | 477 | [root] root->[root] local.a_number (expand) 478 | 479 | 480 | 481 | 482 | 483 | [root] local.a_string (expand) 484 | 485 | [root] local.a_string (expand) 486 | 487 | 488 | 489 | [root] root->[root] local.a_string (expand) 490 | 491 | 492 | 493 | 494 | 495 | [root] local.eq (expand) 496 | 497 | [root] local.eq (expand) 498 | 499 | 500 | 501 | [root] root->[root] local.eq (expand) 502 | 503 | 504 | 505 | 506 | 507 | [root] local.f (expand) 508 | 509 | [root] local.f (expand) 510 | 511 | 512 | 513 | [root] root->[root] local.f (expand) 514 | 515 | 516 | 517 | 518 | 519 | [root] local.formatted (expand) 520 | 521 | [root] local.formatted (expand) 522 | 523 | 524 | 525 | [root] root->[root] local.formatted (expand) 526 | 527 | 528 | 529 | 530 | 531 | [root] local.formatted_list (expand) 532 | 533 | [root] local.formatted_list (expand) 534 | 535 | 536 | 537 | [root] root->[root] local.formatted_list (expand) 538 | 539 | 540 | 541 | 542 | 543 | [root] local.gt (expand) 544 | 545 | [root] local.gt (expand) 546 | 547 | 548 | 549 | [root] root->[root] local.gt (expand) 550 | 551 | 552 | 553 | 554 | 555 | [root] local.gte (expand) 556 | 557 | [root] local.gte (expand) 558 | 559 | 560 | 561 | [root] root->[root] local.gte (expand) 562 | 563 | 564 | 565 | 566 | 567 | [root] local.lcase (expand) 568 | 569 | [root] local.lcase (expand) 570 | 571 | 572 | 573 | [root] root->[root] local.lcase (expand) 574 | 575 | 576 | 577 | 578 | 579 | [root] local.lt (expand) 580 | 581 | [root] local.lt (expand) 582 | 583 | 584 | 585 | [root] root->[root] local.lt (expand) 586 | 587 | 588 | 589 | 590 | 591 | [root] local.lte (expand) 592 | 593 | [root] local.lte (expand) 594 | 595 | 596 | 597 | [root] root->[root] local.lte (expand) 598 | 599 | 600 | 601 | 602 | 603 | [root] local.neq (expand) 604 | 605 | [root] local.neq (expand) 606 | 607 | 608 | 609 | [root] root->[root] local.neq (expand) 610 | 611 | 612 | 613 | 614 | 615 | [root] local.one (expand) 616 | 617 | [root] local.one (expand) 618 | 619 | 620 | 621 | [root] root->[root] local.one (expand) 622 | 623 | 624 | 625 | 626 | 627 | [root] local.t (expand) 628 | 629 | [root] local.t (expand) 630 | 631 | 632 | 633 | [root] root->[root] local.t (expand) 634 | 635 | 636 | 637 | 638 | 639 | [root] local.three (expand) 640 | 641 | [root] local.three (expand) 642 | 643 | 644 | 645 | [root] root->[root] local.three (expand) 646 | 647 | 648 | 649 | 650 | 651 | [root] local.trimmed (expand) 652 | 653 | [root] local.trimmed (expand) 654 | 655 | 656 | 657 | [root] root->[root] local.trimmed (expand) 658 | 659 | 660 | 661 | 662 | 663 | [root] local.two (expand) 664 | 665 | [root] local.two (expand) 666 | 667 | 668 | 669 | [root] root->[root] local.two (expand) 670 | 671 | 672 | 673 | 674 | 675 | [root] local.ucase (expand) 676 | 677 | [root] local.ucase (expand) 678 | 679 | 680 | 681 | [root] root->[root] local.ucase (expand) 682 | 683 | 684 | 685 | 686 | 687 | [root] local.zero (expand) 688 | 689 | [root] local.zero (expand) 690 | 691 | 692 | 693 | [root] root->[root] local.zero (expand) 694 | 695 | 696 | 697 | 698 | 699 | --------------------------------------------------------------------------------