├── m5 ├── terraform_cloud_config │ ├── outputs.tf │ ├── terraform.tf │ ├── main.tf │ └── variables.tf ├── github_config │ ├── outputs.tf │ ├── terraform.tf │ ├── variables.tf │ └── main.tf ├── gitignore.example ├── terraform.yml └── m5_commands.txt ├── m9 ├── create_secrets_manager │ ├── terraform.tfvars.example │ ├── terraform.tf │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── application_config_complete │ ├── .gitignore │ ├── terraform.tf │ ├── keys.tf │ ├── outputs.tf │ ├── templates │ │ ├── application.config.tpl │ │ └── userdata.sh │ ├── datasources.tf │ ├── ssm_parameters.tf │ ├── .github │ │ └── workflows │ │ │ └── terraform.yml │ ├── security_groups.tf │ ├── variables.tf │ └── resources.tf ├── updated_userdata │ └── userdata.sh └── m9_commands.txt ├── m3 ├── network_config_example │ ├── terraform.tf │ ├── outputs.tf │ ├── variables.tf │ ├── imports.tf │ └── resources.tf ├── m3_commands.txt └── cloud_formation_template │ └── vpc_template.yaml ├── m7 ├── application_config_example │ ├── terraform.tf │ ├── terraform.tfvars.example │ ├── keys.tf │ ├── templates │ │ ├── application.config.tpl │ │ └── userdata.sh │ ├── outputs.tf │ ├── datasources.tf │ ├── .github │ │ └── workflows │ │ │ └── terraform.yml │ ├── variables.tf │ ├── security_groups.tf │ └── resources.tf ├── github_config_application │ ├── variables.tf │ ├── terraform.tf │ ├── outputs.tf │ └── main.tf └── m7_commands.txt ├── m4 ├── backend.tf └── m4_commands.txt ├── m8 ├── ansible_updates │ └── userdata.sh └── m8_commands.txt ├── .gitignore ├── m6 └── m6_commands.txt ├── CHANGELOG.md └── README.md /m5/terraform_cloud_config/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /m9/create_secrets_manager/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | api_key = "SOME_VALUE" -------------------------------------------------------------------------------- /m3/network_config_example/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m5/github_config/outputs.tf: -------------------------------------------------------------------------------- 1 | output "remote_url" { 2 | value = github_repository.main.http_clone_url 3 | description = "URL to use when adding remote to local git repo." 4 | } -------------------------------------------------------------------------------- /m5/github_config/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | github = { 4 | source = "integrations/github" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m5/github_config/variables.tf: -------------------------------------------------------------------------------- 1 | variable "repository_name" { 2 | type = string 3 | description = "Name of repository to create" 4 | default = "globo-networking" 5 | } -------------------------------------------------------------------------------- /m5/terraform_cloud_config/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | tfe = { 4 | source = "hashicorp/tfe" 5 | version = ">=0.45.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m9/create_secrets_manager/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m7/application_config_example/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m7/github_config_application/variables.tf: -------------------------------------------------------------------------------- 1 | variable "repository_name" { 2 | type = string 3 | description = "Name of repository to create" 4 | default = "globo-webapp" 5 | } -------------------------------------------------------------------------------- /m7/github_config_application/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | github = { 4 | source = "integrations/github" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /m9/create_secrets_manager/outputs.tf: -------------------------------------------------------------------------------- 1 | output "role_name" { 2 | value = aws_iam_role.web_app.name 3 | } 4 | 5 | output "secret_id" { 6 | value = aws_secretsmanager_secret.api_key.id 7 | } -------------------------------------------------------------------------------- /m7/github_config_application/outputs.tf: -------------------------------------------------------------------------------- 1 | output "remote_url" { 2 | value = github_repository.main.http_clone_url 3 | description = "URL to use when adding remote to local git repo." 4 | } -------------------------------------------------------------------------------- /m3/network_config_example/outputs.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # OUTPUT 3 | ################################################################################## 4 | -------------------------------------------------------------------------------- /m7/application_config_example/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | prefix = "globo-dev" 2 | environment = "testing" 3 | billing_code = "8652147555" 4 | public_subnets = [ "SUBNET_1","SUBNET_2" ] 5 | vpc_id = "VPC_ID" 6 | api_key = "Tac0$AreDel!c1ous" -------------------------------------------------------------------------------- /m5/gitignore.example: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | 11 | # .tfplan files 12 | *.tfplan 13 | 14 | # .pem files 15 | *.pem -------------------------------------------------------------------------------- /m9/application_config_complete/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | 11 | # .tfplan files 12 | *.tfplan 13 | 14 | # .pem files 15 | *.pem -------------------------------------------------------------------------------- /m9/application_config_complete/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5.0" 6 | } 7 | tfe = { 8 | source = "hashicorp/tfe" 9 | version = "~> 0.0" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /m4/backend.tf: -------------------------------------------------------------------------------- 1 | ## Move this backend file to your network config when migrating state 2 | terraform { 3 | cloud { 4 | # Organization ID 5 | organization = "ORGANIZATION_NAME" 6 | # Workspace ID 7 | workspaces { 8 | name = "web-network-dev" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /m7/application_config_example/keys.tf: -------------------------------------------------------------------------------- 1 | # Create SSH Key pair for aws instances using a module 2 | module "ssh_keys" { 3 | source = "terraform-aws-modules/key-pair/aws" 4 | version = "~>2.0.0" 5 | 6 | key_name = "${local.name_prefix}-tdd-keys" 7 | create_private_key = true 8 | } 9 | -------------------------------------------------------------------------------- /m9/application_config_complete/keys.tf: -------------------------------------------------------------------------------- 1 | # Create SSH Key pair for aws instances using a module 2 | module "ssh_keys" { 3 | source = "terraform-aws-modules/key-pair/aws" 4 | version = "~>2.0.0" 5 | 6 | key_name = "${local.name_prefix}-tdd-keys" 7 | create_private_key = true 8 | } 9 | -------------------------------------------------------------------------------- /m7/application_config_example/templates/application.config.tpl: -------------------------------------------------------------------------------- 1 | # Change every time hosts are added or removed 2 | host_list { 3 | %{ for host in hosts ~} 4 | hostname ${host} 5 | %{ endfor ~} 6 | } 7 | 8 | app_config { 9 | site_name = "${site_name}" 10 | api_key = "${api_key}" 11 | } -------------------------------------------------------------------------------- /m7/application_config_example/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public_lb_dns" { 2 | value = aws_lb.main.dns_name 3 | } 4 | 5 | output "webapp_instance0_public_ip" { 6 | value = aws_instance.main[0].public_ip 7 | } 8 | 9 | output "private_key_pem" { 10 | value = nonsensitive(module.ssh_keys.private_key_pem) 11 | } -------------------------------------------------------------------------------- /m9/application_config_complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public_lb_dns" { 2 | value = aws_lb.main.dns_name 3 | } 4 | 5 | output "webapp_instance0_public_ip" { 6 | value = aws_instance.main[0].public_ip 7 | } 8 | 9 | output "private_key_pem" { 10 | value = nonsensitive(module.ssh_keys.private_key_pem) 11 | } -------------------------------------------------------------------------------- /m9/application_config_complete/templates/application.config.tpl: -------------------------------------------------------------------------------- 1 | # Change every time hosts are added or removed 2 | host_list { 3 | %{ for host in hosts ~} 4 | hostname ${host} 5 | %{ endfor ~} 6 | } 7 | 8 | app_config { 9 | site_name = "${site_name}" 10 | api_key = "${api_key}" 11 | } -------------------------------------------------------------------------------- /m8/ansible_updates/userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo yum update 3 | sudo yum install git -y 4 | sudo amazon-linux-extras install ansible2 -y 5 | sudo mkdir /var/ansible_playbooks 6 | sudo git clone ${playbook_repository} /var/ansible_playbooks 7 | ansible-playbook /var/ansible_playbooks/playbook.yml -i /var/ansible_playbooks/hosts -------------------------------------------------------------------------------- /m9/create_secrets_manager/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = string 3 | description = "(Optional) AWS Region to deploy in. Defaults to us-east-1." 4 | default = "us-east-1" 5 | } 6 | 7 | variable "api_key" { 8 | type = string 9 | description = "(Required) String to use for API key" 10 | sensitive = true 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | 11 | # .tfplan files 12 | *.tfplan 13 | 14 | # .pem files 15 | *.pem 16 | 17 | # Consul data directories 18 | **/network_config/* 19 | **/application_config/* 20 | 21 | # .hcl files 22 | *.hcl -------------------------------------------------------------------------------- /m7/application_config_example/datasources.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # DATA SOURCES 3 | ################################################################################## 4 | 5 | data "aws_ssm_parameter" "amzn2_linux" { 6 | name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" 7 | } -------------------------------------------------------------------------------- /m5/terraform_cloud_config/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | # Configuration options 3 | hostname = var.hostname 4 | } 5 | 6 | resource "tfe_oauth_client" "github" { 7 | name = var.oauth_name 8 | organization = var.organization 9 | api_url = "https://api.github.com" 10 | http_url = "https://github.com" 11 | oauth_token = var.gh_pat 12 | service_provider = "github" 13 | } -------------------------------------------------------------------------------- /m7/application_config_example/templates/userdata.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | sudo amazon-linux-extras install -y nginx1 3 | sudo service nginx start 4 | sudo rm /usr/share/nginx/html/index.html 5 | echo 'Taco Wagon Server

You did it! Have a 🌮

' | sudo tee /usr/share/nginx/html/index.html -------------------------------------------------------------------------------- /m9/application_config_complete/datasources.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # DATA SOURCES 3 | ################################################################################## 4 | 5 | data "tfe_outputs" "networking" { 6 | organization = var.tfe_organization 7 | workspace = var.tfe_workspace_name 8 | } 9 | 10 | data "aws_ssm_parameter" "amzn2_linux" { 11 | name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" 12 | } -------------------------------------------------------------------------------- /m5/terraform_cloud_config/variables.tf: -------------------------------------------------------------------------------- 1 | variable "hostname" { 2 | type = string 3 | description = "The Terraform Cloud/Enterprise hostname to connect to" 4 | default = "app.terraform.io" 5 | } 6 | 7 | variable "oauth_name" { 8 | type = string 9 | default = "tdd-github" 10 | } 11 | 12 | variable "organization" { 13 | type = string 14 | description = "Terraform Cloud organization" 15 | } 16 | 17 | variable "gh_pat" { 18 | type = string 19 | description = "Github Personal Access token" 20 | } -------------------------------------------------------------------------------- /m5/github_config/main.tf: -------------------------------------------------------------------------------- 1 | resource "github_repository" "main" { 2 | name = var.repository_name 3 | description = "Terraform Deep Dive Repository for Globomantics Networking" 4 | visibility = "public" 5 | auto_init = true 6 | gitignore_template = "Terraform" 7 | } 8 | 9 | resource "github_branch" "main" { 10 | repository = github_repository.main.name 11 | branch = "main" 12 | } 13 | 14 | resource "github_branch_default" "default" { 15 | repository = github_repository.main.name 16 | branch = github_branch.main.branch 17 | } -------------------------------------------------------------------------------- /m7/github_config_application/main.tf: -------------------------------------------------------------------------------- 1 | resource "github_repository" "main" { 2 | name = var.repository_name 3 | description = "Terraform Deep Dive Repository for Globomantics Application" 4 | visibility = "public" 5 | auto_init = true 6 | gitignore_template = "Terraform" 7 | } 8 | 9 | resource "github_branch" "main" { 10 | repository = github_repository.main.name 11 | branch = "main" 12 | } 13 | 14 | resource "github_branch_default" "default" { 15 | repository = github_repository.main.name 16 | branch = github_branch.main.branch 17 | } -------------------------------------------------------------------------------- /m6/m6_commands.txt: -------------------------------------------------------------------------------- 1 | # Get your local repository up to date 2 | cd network_config 3 | git checkout main 4 | git pull 5 | 6 | # Create a new branch for adding the workspace tag and removing variable defaults 7 | git checkout -b add-workspace-tag 8 | 9 | # Make code changes 10 | # Commit changes to branch 11 | git add . 12 | git commit -m "Add workspace tag and make variables required" 13 | 14 | # Push changes to GitHub 15 | git push --set-upstream origin add-workspace-tag 16 | 17 | # Head over to GitHub for the rest of the workflow 18 | 19 | # Push an empty commit 20 | git commit --allow-empty -m "Trigger workflow" 21 | git push 22 | -------------------------------------------------------------------------------- /m9/updated_userdata/userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo yum update 3 | sudo yum install git -y 4 | sudo amazon-linux-extras install ansible2 -y 5 | sudo mkdir /var/ansible_playbooks 6 | sudo git clone ${playbook_repository} /var/ansible_playbooks 7 | aws secretsmanager get-secret-value --secret-id "${secret_id}" --region us-east-1 --query SecretString --output text > /var/ansible_playbooks/api_key.txt 8 | aws ssm get-parameter --name "${host_list_ssm_name}" --region us-east-1 --query Parameter.Value --output text > /var/ansible_playbooks/host_list.txt 9 | aws ssm get-parameter --name "${site_name_ssm_name}" --region us-east-1 --query Parameter.Value --output text > /var/ansible_playbooks/site_name.txt 10 | ansible-playbook /var/ansible_playbooks/playbook.yml -i /var/ansible_playbooks/hosts -------------------------------------------------------------------------------- /m9/application_config_complete/templates/userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo yum update 3 | sudo yum install git -y 4 | sudo amazon-linux-extras install ansible2 -y 5 | sudo mkdir /var/ansible_playbooks 6 | sudo git clone ${playbook_repository} /var/ansible_playbooks 7 | aws secretsmanager get-secret-value --secret-id "${secret_id}" --region us-east-1 --query SecretString --output text > /var/ansible_playbooks/api_key.txt 8 | aws ssm get-parameter --name "${host_list_ssm_name}" --region us-east-1 --query Parameter.Value --output text > /var/ansible_playbooks/host_list.txt 9 | aws ssm get-parameter --name "${site_name_ssm_name}" --region us-east-1 --query Parameter.Value --output text > /var/ansible_playbooks/site_name.txt 10 | ansible-playbook /var/ansible_playbooks/playbook.yml -i /var/ansible_playbooks/hosts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | **2023-08-11** 2 | 3 | FULL COURSE UPDATE - v3 4 | 5 | This is a full update of the course. The default branch for the repository is now `main`. The `v2` branch will be kept for historical purposes. V3 is a complete overhaul of V2, so the code is not compatible with V2 of the Deep Dive course. 6 | 7 | **2020-12-21** 8 | 9 | ISSUES fixed 10 | * Removed backend.tf file from m3 (not supposed to be there until you copy it over in m4) 11 | * Added command to install `jq` if it's missing in the `junior_admin.sh` script in m3 12 | * Added quotes for import commands in the `ImportCOmmands.txt` file in m3 13 | * Removed route table ID references (they should not have been there) 14 | 15 | 0.14.3 Updates 16 | * Change VPC module version to support 0.14 of Terraform 17 | * Update the configurations to use the `terraform` and `required_providers` blocks 18 | * exclude `hcl` files in `.gitignore` 19 | * Add quota increase request 20 | -------------------------------------------------------------------------------- /m9/m9_commands.txt: -------------------------------------------------------------------------------- 1 | # Set AWS_PROFILE 2 | 3 | # Linux or MacOS 4 | export AWS_PROFILE=deep-dive 5 | 6 | # Windows 7 | $env:AWS_PROFILE="deep-dive" 8 | 9 | # Move API Key to secrets manager 10 | # Create secrets manager and ec2 role with Terraform 11 | cd m9/create_secrets_manager 12 | terraform init 13 | 14 | # Set the API key value in terraform.tfvars 15 | terraform apply 16 | 17 | # Note the role name and secret_id 18 | 19 | # Create a new branch 20 | cd ../../application_config 21 | git checkout main 22 | git pull 23 | git checkout -b add-secrets-manager 24 | 25 | # Update code to remove the terraform_data block 26 | # Add variable for role name and secret ID 27 | # Remove variable for api key 28 | # Add instance profile resource 29 | # Update iam_instance to use profile 30 | # Add secret id to userdata script and templatefile function 31 | 32 | # Commit changes 33 | git add . 34 | git commit -m "Switch to using secrets manager" 35 | git push --set-upstream origin add-secrets-manager 36 | 37 | # Update variables in workspace 38 | # Create pull request and merge if it looks right 39 | -------------------------------------------------------------------------------- /m3/network_config_example/variables.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # VARIABLES 3 | ################################################################################## 4 | 5 | variable "region" { 6 | type = string 7 | description = "(Optional) AWS Region to use. Default: us-east-1" 8 | default = "us-east-1" 9 | } 10 | 11 | variable "prefix" { 12 | type = string 13 | description = "(Optional) Prefix to use for all resources in this module. Default: globo-dev" 14 | default = "globo-dev" 15 | } 16 | 17 | variable "cidr_block" { 18 | type = string 19 | description = "(Optional) The CIDR block for the VPC. Default:10.42.0.0/16" 20 | default = "10.42.0.0/16" 21 | } 22 | 23 | variable "public_subnets" { 24 | type = map(string) 25 | description = "(Optional) Map of public subnets to create with CIDR blocks. Key will be used as subnet name with prefix. Default: {subnet-1 =" 26 | default = { 27 | public-1 = "10.42.10.0/24" 28 | public-2 = "10.42.11.0/24" 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /m9/application_config_complete/ssm_parameters.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | host_list_ssm_name = "/${local.name_prefix}/host-list" 3 | site_name_ssm_name = "/${local.name_prefix}/site-name" 4 | } 5 | 6 | resource "aws_ssm_parameter" "host_list" { 7 | name = local.host_list_ssm_name 8 | type = "StringList" 9 | value = join(",", aws_instance.main.*.private_dns) 10 | } 11 | 12 | resource "aws_ssm_parameter" "site_name" { 13 | name = local.site_name_ssm_name 14 | type = "String" 15 | value = "${local.name_prefix}-taco-wagon" 16 | } 17 | 18 | data "aws_iam_policy_document" "ssm_access" { 19 | statement { 20 | effect = "Allow" 21 | actions = ["ssm:GetParameter"] 22 | resources = [aws_ssm_parameter.host_list.arn, aws_ssm_parameter.site_name.arn] 23 | } 24 | } 25 | 26 | resource "aws_iam_policy" "ssm_access" { 27 | name = "${local.name_prefix}-ssm-access" 28 | policy = data.aws_iam_policy_document.ssm_access.json 29 | } 30 | 31 | resource "aws_iam_role_policy_attachment" "ssm_access" { 32 | role = var.ec2_role_name 33 | policy_arn = aws_iam_policy.ssm_access.arn 34 | } -------------------------------------------------------------------------------- /m8/m8_commands.txt: -------------------------------------------------------------------------------- 1 | # Start a new branch for ansible changes 2 | git checkout main 3 | git pull 4 | git checkout -b ansible-change 5 | 6 | # Don't forget to fmt and validate changes! 7 | 8 | # Copy the userdata update over 9 | cp ../m8/ansible_updates/userdata.sh ./templates/userdata.sh 10 | 11 | # Make code changes to use ansible instead of provisioners 12 | # Repository url is https://github.com/ned1313/ansible-playbook-nginx.git 13 | # Commit changes and push to origin 14 | git add . 15 | git commit -m "Switch to using Ansible for nginx install" 16 | git push --set-upstream origin ansible-change 17 | 18 | # Add playbook variable to workspace 19 | # Create pull request and review results 20 | 21 | # Updating the null_resource 22 | # Create a new branch called terraform-data 23 | git checkout main 24 | git pull 25 | git checkout -b terraform-data 26 | 27 | # Switch out the null_resource for terraform_data 28 | # Commit changes and push to origin 29 | git add . 30 | git commit -m "Switch to using terraform_data" 31 | git push --set-upstream origin terraform-data 32 | 33 | # Create pull request and merge changes to development 34 | # Merge changes to main also 35 | -------------------------------------------------------------------------------- /m4/m4_commands.txt: -------------------------------------------------------------------------------- 1 | # Make sure your AWS profile is set 2 | # Linux or MacOS 3 | export AWS_PROFILE=deep-dive 4 | 5 | # Windows 6 | $env:AWS_PROFILE="deep-dive" 7 | 8 | ## First let's try out some terraform state commands 9 | ## Go to the network_config folder and run the state commands 10 | 11 | # View all the Terraform resources 12 | terraform state list 13 | 14 | # Now let's look at a specific resource 15 | terraform state show module.main.aws_vpc.this[0] 16 | 17 | # We can also view all the state data 18 | terraform state pull 19 | 20 | ## Now it's time to migrate our state data to Terraform cloud 21 | ## Create a terraform cloud account and organization called deep-dive- 22 | ## Sign up for an account here: https://app.terraform.io/public/signup/account 23 | 24 | ## Login into Terraform Cloud to get an user access token 25 | terraform login 26 | 27 | # Copy the backend file from the m4 directory to the network_config 28 | cp ../m4/backend.tf . 29 | 30 | # Update the backend info and run Terraform init to migrate state data 31 | terraform init 32 | 33 | # Now run a Terraform plan and note that it fails 34 | terraform plan 35 | 36 | # After adding AWS credentials, run a Terraform apply 37 | terraform apply 38 | -------------------------------------------------------------------------------- /m9/create_secrets_manager/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | resource "aws_secretsmanager_secret" "api_key" { 6 | name = "taco_wagon_dev_api_key" 7 | } 8 | 9 | resource "aws_secretsmanager_secret_version" "api_key" { 10 | secret_id = aws_secretsmanager_secret.api_key.id 11 | secret_string = var.api_key 12 | } 13 | 14 | data "aws_iam_policy_document" "web_app_access" { 15 | statement { 16 | principals { 17 | type = "AWS" 18 | identifiers = [aws_iam_role.web_app.arn] 19 | } 20 | 21 | actions = ["secretsmanager:GetSecretValue"] 22 | 23 | resources = ["*"] 24 | } 25 | } 26 | 27 | resource "aws_secretsmanager_secret_policy" "api_key" { 28 | secret_arn = aws_secretsmanager_secret.api_key.arn 29 | policy = data.aws_iam_policy_document.web_app_access.json 30 | } 31 | 32 | resource "aws_iam_role" "web_app" { 33 | name = "web_app_dev_api_key_access" 34 | 35 | assume_role_policy = jsonencode({ 36 | Version = "2012-10-17" 37 | Statement = [ 38 | { 39 | Action = "sts:AssumeRole" 40 | Effect = "Allow" 41 | Sid = "" 42 | Principal = { 43 | Service = "ec2.amazonaws.com" 44 | } 45 | }, 46 | ] 47 | }) 48 | } -------------------------------------------------------------------------------- /m5/terraform.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform' 2 | 3 | on: push 4 | 5 | env: 6 | TF_LOG: INFO 7 | TF_INPUT: false 8 | 9 | jobs: 10 | terraform: 11 | name: 'Terraform' 12 | runs-on: ubuntu-latest 13 | 14 | # Use the Bash shell regardless whether the GitHub Actions runner is 15 | # ubuntu-latest, macos-latest, or windows-latest 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | steps: 21 | # Checkout the repository to the GitHub Actions runner 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | # Install the preferred version of Terraform CLI 26 | - name: Setup Terraform 27 | uses: hashicorp/setup-terraform@v2 28 | 29 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 30 | - name: Terraform Init 31 | id: init 32 | run: terraform init 33 | 34 | # Run a terraform fmt for push 35 | - name: Terraform Format 36 | id: fmt 37 | run: terraform fmt -check 38 | 39 | # Run a terraform validate 40 | # Run even if formatting fails 41 | - name: Terraform Validate 42 | id: validate 43 | if: (success() || failure()) 44 | run: terraform validate 45 | -------------------------------------------------------------------------------- /m7/application_config_example/.github/workflows/terraform.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform' 2 | 3 | on: push 4 | 5 | env: 6 | TF_LOG: INFO 7 | TF_INPUT: false 8 | 9 | jobs: 10 | terraform: 11 | name: 'Terraform' 12 | runs-on: ubuntu-latest 13 | 14 | # Use the Bash shell regardless whether the GitHub Actions runner is 15 | # ubuntu-latest, macos-latest, or windows-latest 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | steps: 21 | # Checkout the repository to the GitHub Actions runner 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | # Install the preferred version of Terraform CLI 26 | - name: Setup Terraform 27 | uses: hashicorp/setup-terraform@v2 28 | 29 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 30 | - name: Terraform Init 31 | id: init 32 | run: terraform init 33 | 34 | # Run a terraform fmt for push 35 | - name: Terraform Format 36 | id: fmt 37 | run: terraform fmt -check 38 | 39 | # Run a terraform validate 40 | # Run even if formatting fails 41 | - name: Terraform Validate 42 | id: validate 43 | if: (success() || failure()) 44 | run: terraform validate 45 | -------------------------------------------------------------------------------- /m9/application_config_complete/.github/workflows/terraform.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform' 2 | 3 | on: push 4 | 5 | env: 6 | TF_LOG: INFO 7 | TF_INPUT: false 8 | 9 | jobs: 10 | terraform: 11 | name: 'Terraform' 12 | runs-on: ubuntu-latest 13 | 14 | # Use the Bash shell regardless whether the GitHub Actions runner is 15 | # ubuntu-latest, macos-latest, or windows-latest 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | steps: 21 | # Checkout the repository to the GitHub Actions runner 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | # Install the preferred version of Terraform CLI 26 | - name: Setup Terraform 27 | uses: hashicorp/setup-terraform@v2 28 | 29 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 30 | - name: Terraform Init 31 | id: init 32 | run: terraform init 33 | 34 | # Run a terraform fmt for push 35 | - name: Terraform Format 36 | id: fmt 37 | run: terraform fmt -check 38 | 39 | # Run a terraform validate 40 | # Run even if formatting fails 41 | - name: Terraform Validate 42 | id: validate 43 | if: (success() || failure()) 44 | run: terraform validate 45 | -------------------------------------------------------------------------------- /m3/network_config_example/imports.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # IMPORTS 3 | ################################################################################## 4 | 5 | import { 6 | to = module.main.aws_vpc.this[0] 7 | id = "VPC" #VPC 8 | } 9 | 10 | import { 11 | to = module.main.aws_subnet.public[0] 12 | id = "PublicSubnet1" #PublicSubnet1 13 | } 14 | 15 | import { 16 | to = module.main.aws_subnet.public[1] 17 | id = "PublicSubnet2" #PublicSubnet2 18 | } 19 | 20 | import { 21 | to = module.main.aws_internet_gateway.this[0] 22 | id = "InternetGateway" #InternetGateway 23 | } 24 | 25 | import { 26 | to = module.main.aws_route.public_internet_gateway[0] 27 | id = "DefaultPublicRoute" #DefaultPublicRoute 28 | } 29 | 30 | import { 31 | to = module.main.aws_route_table.public[0] 32 | id = "PublicRouteTable" #PublicRouteTable 33 | } 34 | 35 | import { 36 | to = module.main.aws_route_table_association.public[0] 37 | id = "PublicSubnet1/PublicRouteTable" #PublicSubnet1/PublicRouteTable 38 | } 39 | 40 | import { 41 | to = module.main.aws_route_table_association.public[1] 42 | id = "PublicSubnet2/PublicRouteTable" #PublicSubnet2/PublicRouteTable 43 | } 44 | 45 | import { 46 | to = aws_security_group.ingress 47 | id = "NoIngressSecurityGroup" #NoIngressSecurityGroup 48 | } 49 | -------------------------------------------------------------------------------- /m7/application_config_example/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "region" { 3 | type = string 4 | description = "(Optional) AWS Region to deploy in. Defaults to us-east-1." 5 | default = "us-east-1" 6 | } 7 | 8 | variable "prefix" { 9 | type = string 10 | description = "(Required) Prefix to use for all resources in this module." 11 | } 12 | 13 | variable "environment" { 14 | type = string 15 | description = "(Required) Environment of all resources" 16 | } 17 | 18 | variable "billing_code" { 19 | type = string 20 | description = "(Required) Billing code for network resources" 21 | } 22 | 23 | # Application variables 24 | 25 | variable "ip_range" { 26 | default = "0.0.0.0/0" 27 | } 28 | 29 | variable "instance_type" { 30 | type = string 31 | description = "(Optional) EC2 Instance type to use for web app. Defaults to t3.micro." 32 | default = "t3.micro" 33 | } 34 | 35 | variable "api_key" { 36 | type = string 37 | description = "(Required) API key for web app to talk to SaaS platform." 38 | } 39 | 40 | variable "public_subnets" { 41 | type = list(string) 42 | description = "(Required) List of subnet IDs for EC2 instance deployments." 43 | } 44 | 45 | variable "vpc_id" { 46 | type = string 47 | description = "(Required) VPC ID of VPC for application deployment." 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /m3/network_config_example/resources.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # PROVIDERS 3 | ################################################################################## 4 | 5 | provider "aws" { 6 | region = var.region 7 | } 8 | 9 | ################################################################################## 10 | # DATA 11 | ################################################################################## 12 | 13 | data "aws_availability_zones" "available" {} 14 | 15 | ################################################################################## 16 | # RESOURCES 17 | ################################################################################## 18 | locals { 19 | common_tags = { 20 | 21 | } 22 | } 23 | 24 | module "main" { 25 | source = "terraform-aws-modules/vpc/aws" 26 | version = "5.0.0" 27 | 28 | name = var.prefix 29 | cidr = var.cidr_block 30 | 31 | azs = slice(data.aws_availability_zones.available.names, 0, length(var.public_subnets)) 32 | public_subnets = [for k, v in var.public_subnets : v] 33 | public_subnet_names = [for k, v in var.public_subnets : "${var.prefix}-${k}"] 34 | enable_dns_hostnames = true 35 | public_subnet_suffix = "" 36 | public_route_table_tags = { Name = "${var.prefix}-public" } 37 | map_public_ip_on_launch = true 38 | 39 | enable_nat_gateway = false 40 | 41 | tags = local.common_tags 42 | } -------------------------------------------------------------------------------- /m7/application_config_example/security_groups.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # RESOURCES 3 | ################################################################################## 4 | 5 | resource "aws_security_group" "webapp_http_inbound_sg" { 6 | name = "${local.name_prefix}-http-inbound" 7 | description = "Allow HTTP from Anywhere" 8 | 9 | ingress { 10 | from_port = 80 11 | to_port = 80 12 | protocol = "tcp" 13 | cidr_blocks = ["0.0.0.0/0"] 14 | } 15 | 16 | egress { 17 | from_port = 0 18 | to_port = 0 19 | protocol = "-1" 20 | cidr_blocks = ["0.0.0.0/0"] 21 | } 22 | 23 | vpc_id = var.vpc_id 24 | 25 | tags = local.common_tags 26 | } 27 | 28 | resource "aws_security_group" "webapp_ssh_inbound_sg" { 29 | name = "${local.name_prefix}-ssh-inbound" 30 | description = "Allow SSH from certain ranges" 31 | 32 | ingress { 33 | from_port = 22 34 | to_port = 22 35 | protocol = "tcp" 36 | cidr_blocks = [var.ip_range] 37 | } 38 | 39 | vpc_id = var.vpc_id 40 | 41 | tags = local.common_tags 42 | } 43 | 44 | resource "aws_security_group" "webapp_outbound_sg" { 45 | name = "${local.name_prefix}-webapp-outbound" 46 | description = "Allow outbound connections" 47 | 48 | egress { 49 | from_port = 0 50 | to_port = 0 51 | protocol = "-1" 52 | cidr_blocks = ["0.0.0.0/0"] 53 | } 54 | 55 | vpc_id = var.vpc_id 56 | 57 | tags = local.common_tags 58 | } -------------------------------------------------------------------------------- /m9/application_config_complete/security_groups.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # RESOURCES 3 | ################################################################################## 4 | 5 | resource "aws_security_group" "webapp_http_inbound_sg" { 6 | name = "${local.name_prefix}-http-inbound" 7 | description = "Allow HTTP from Anywhere" 8 | 9 | ingress { 10 | from_port = 80 11 | to_port = 80 12 | protocol = "tcp" 13 | cidr_blocks = ["0.0.0.0/0"] 14 | } 15 | 16 | egress { 17 | from_port = 0 18 | to_port = 0 19 | protocol = "-1" 20 | cidr_blocks = ["0.0.0.0/0"] 21 | } 22 | 23 | vpc_id = data.tfe_outputs.networking.nonsensitive_values.vpc_id 24 | 25 | tags = local.common_tags 26 | } 27 | 28 | resource "aws_security_group" "webapp_ssh_inbound_sg" { 29 | name = "${local.name_prefix}-ssh-inbound" 30 | description = "Allow SSH from certain ranges" 31 | 32 | ingress { 33 | from_port = 22 34 | to_port = 22 35 | protocol = "tcp" 36 | cidr_blocks = [var.ip_range] 37 | } 38 | 39 | vpc_id = data.tfe_outputs.networking.nonsensitive_values.vpc_id 40 | 41 | tags = local.common_tags 42 | } 43 | 44 | resource "aws_security_group" "webapp_outbound_sg" { 45 | name = "${local.name_prefix}-webapp-outbound" 46 | description = "Allow outbound connections" 47 | 48 | egress { 49 | from_port = 0 50 | to_port = 0 51 | protocol = "-1" 52 | cidr_blocks = ["0.0.0.0/0"] 53 | } 54 | 55 | vpc_id = data.tfe_outputs.networking.nonsensitive_values.vpc_id 56 | 57 | tags = local.common_tags 58 | } -------------------------------------------------------------------------------- /m9/application_config_complete/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "region" { 3 | type = string 4 | description = "(Optional) AWS Region to deploy in. Defaults to us-east-1." 5 | default = "us-east-1" 6 | } 7 | 8 | variable "prefix" { 9 | type = string 10 | description = "(Required) Prefix to use for all resources in this module." 11 | } 12 | 13 | variable "environment" { 14 | type = string 15 | description = "(Required) Environment of all resources" 16 | } 17 | 18 | variable "billing_code" { 19 | type = string 20 | description = "(Required) Billing code for network resources" 21 | } 22 | 23 | # Application variables 24 | 25 | variable "ip_range" { 26 | default = "0.0.0.0/0" 27 | } 28 | 29 | variable "instance_type" { 30 | type = string 31 | description = "(Optional) EC2 Instance type to use for web app. Defaults to t3.micro." 32 | default = "t3.micro" 33 | } 34 | 35 | variable "api_key_secret_id" { 36 | type = string 37 | description = "(Required) Secret ID for API key for web app to talk to SaaS platform." 38 | } 39 | 40 | variable "playbook_repository" { 41 | type = string 42 | description = "(Required) URL of Ansible playbook repository." 43 | } 44 | 45 | variable "ec2_role_name" { 46 | type = string 47 | description = "(Required) Name of role to associate with EC2 instance profile." 48 | } 49 | 50 | # TFC variables 51 | variable "tfe_organization" { 52 | type = string 53 | description = "(Required) Name of TFC organization." 54 | } 55 | 56 | variable "tfe_workspace_name" { 57 | type = string 58 | description = "(Required) Name of networking workspace to get information." 59 | } 60 | -------------------------------------------------------------------------------- /m3/m3_commands.txt: -------------------------------------------------------------------------------- 1 | # Configure an AWS profile with proper credentials 2 | aws configure --profile deep-dive 3 | 4 | # Linux or MacOS 5 | export AWS_PROFILE=deep-dive 6 | 7 | # Windows 8 | $env:AWS_PROFILE="deep-dive" 9 | 10 | # Deploy the current environment using CloudFormation 11 | cd ./m3/cloud_formation_template 12 | aws cloudformation deploy --template-file="vpc_template.yaml" --stack-name dev-net --parameter-overrides EnvironmentName=globo-dev 13 | aws cloudformation describe-stacks --stack-name dev-net --query 'Stacks[0].Outputs[].[OutputKey, OutputValue]' --output table > table.txt 14 | 15 | # Copy the network config to the root directory 16 | cd ../.. # Move up to the root directory 17 | 18 | ## PowerShell 19 | copy .\m3\network_config_example\ .\network_config -Recurse 20 | 21 | ## Bash or zsh 22 | cp ./m3/network_config_example ./network_config 23 | 24 | # Retrieve the values to use in the import blocks 25 | # They are in the table.txt file in the cloud_formation_template directory 26 | 27 | # Now run a plan and see what happens 28 | cd ./network_config 29 | terraform init 30 | terraform plan 31 | 32 | # Looks like we need to create another object 33 | terraform plan -generate-config-out="generated.tf" 34 | 35 | # Update the VPC ID reference in the generated block to use the module VPC ID 36 | # Move the block to the resources file and delete the generated file 37 | 38 | # Run a plan again and note the imports and adds 39 | 40 | terraform plan 41 | 42 | # There should be 9 imports and 3 adds for default objects 43 | 44 | terraform apply 45 | 46 | # Add the environment input variable and tag 47 | # and update the tags on the security group 48 | terraform plan -out="tags.tfplan" 49 | terraform apply tags.tfplan -------------------------------------------------------------------------------- /m5/m5_commands.txt: -------------------------------------------------------------------------------- 1 | # Start by setting the GitHub token environment variable 2 | # PowerShell 3 | $env:GITHUB_TOKEN="TOKEN_VALUE" 4 | 5 | # bash or zsh 6 | export GITHUB_TOKEN=TOKEN_VALUE 7 | 8 | # Head into the m5/github_config directory 9 | cd ./m5/github_config 10 | 11 | # initialize and apply the Terraform config 12 | terraform init 13 | terraform apply 14 | 15 | # Head into the network_config directory 16 | cd ../../network_config 17 | 18 | # Run git init 19 | git init --initial-branch=main 20 | 21 | # Copy the example gitignore file 22 | cp ../m5/gitignore.example .gitignore 23 | 24 | # Add files to git 25 | git add . 26 | 27 | # Commit files to git 28 | git commit -m "Initial commit" 29 | 30 | # Add remote origin 31 | git remote add origin ORIGIN_URL 32 | 33 | # Track main branch 34 | git fetch 35 | git branch --set-upstream-to="origin/main" main 36 | 37 | # Push code to GitHub 38 | git push origin --force 39 | 40 | # Create the github actions folders 41 | mkdir .github 42 | mkdir .github/workflows 43 | 44 | # Copy the terraform.yml file to the workflows folder 45 | cp ../m5/terraform.yml ./.github/workflows/ 46 | 47 | # Rename the backend file to backend_local.tf 48 | mv backend.tf backend_local.tf 49 | 50 | # Add backend_local.tf to the .gitignore file 51 | # Add the changes to git 52 | git add . 53 | 54 | # Commit the changes 55 | git commit -m "Add CI workflow" 56 | 57 | # Push the changes to GitHub 58 | git push 59 | 60 | # Fix formatting and push updates 61 | terraform fmt 62 | git add . 63 | git commit -m "Fix formatting" 64 | git push 65 | 66 | # Setting up Terraform Cloud to GitHub connection 67 | # Go to the m5/terraform_cloud_config directory 68 | cd ../m5/terraform_cloud_config 69 | terraform init 70 | 71 | # Replace the organization name with your org name 72 | # PowerShell environment variable 73 | terraform apply -var="gh_pat=$env:GITHUB_TOKEN" -var="organization=ORG_NAME" 74 | 75 | # Bash or zsh environment variable 76 | terraform apply -var="gh_pat=$GITHUB_TOKEN" -var="organization=ORG_NAME" 77 | 78 | # Create a new branch called add-third-subnet 79 | git checkout -b add-third-subnet 80 | 81 | # Commit code changes to branch 82 | git add . 83 | git commit -m "Add third subnet and tags" 84 | git push --set-upstream origin add-third-subnet 85 | 86 | # Add variable values before creating pull request -------------------------------------------------------------------------------- /m7/m7_commands.txt: -------------------------------------------------------------------------------- 1 | # Set AWS_PROFILE 2 | 3 | # Linux or MacOS 4 | export AWS_PROFILE=deep-dive 5 | 6 | # Windows 7 | $env:AWS_PROFILE="deep-dive" 8 | 9 | ## PowerShell 10 | copy .\m7\application_config_example\ .\application_config -Recurse 11 | 12 | ## Bash or zsh 13 | cp -r ./m7/application_config_example ./application_config 14 | 15 | ## Create Git Repository 16 | # Start by setting the GitHub token environment variable 17 | # PowerShell 18 | $env:GITHUB_TOKEN="TOKEN_VALUE" 19 | 20 | # bash or zsh 21 | export GITHUB_TOKEN=TOKEN_VALUE 22 | 23 | # Head into the m7/github_config_application directory 24 | cd ./m7/github_config_application 25 | 26 | # initialize and apply the Terraform config 27 | terraform init 28 | terraform apply 29 | 30 | # Head into the application_config directory 31 | cd ../../application_config 32 | 33 | # Run git init 34 | git init --initial-branch=main 35 | 36 | # Copy the example gitignore file 37 | cp ../m5/gitignore.example .gitignore 38 | 39 | # Add files to git 40 | git add . 41 | 42 | # Commit files to git 43 | git commit -m "Initial commit" 44 | 45 | # Add remote origin 46 | git remote add origin ORIGIN_URL 47 | 48 | # Track main branch 49 | git fetch 50 | git branch --set-upstream-to="origin/main" main 51 | 52 | # Push code to GitHub 53 | git push origin --force 54 | 55 | # Make sure checks complete 56 | # Add new workspace to Terraform Cloud linked to Repository 57 | # Add the necessary variable inputs using the portal or commands below 58 | aws ec2 describe-vpcs --region=us-east-1 --filters "Name=tag:Name, Values=globo-dev" --query Vpcs[].VpcId --output text 59 | aws ec2 describe-subnets --region=us-east-1 --filters "Name=vpc-id,Values=vpc-0fa3eb2daaeae0e29" --query Subnets[].SubnetId 60 | 61 | # Check config of first EC2 instance 62 | ssh -i PATH_TO_PEM_FILE ec2-user@PUBLIC_IP_ADDRESS 63 | 64 | # Switch to networking config 65 | cd ../network_config 66 | 67 | # Create a new branch called tfe-outputs 68 | git checkout main 69 | git pull 70 | 71 | git checkout -b tfe-outputs 72 | 73 | # Make code changes 74 | # Commit code changes to branch 75 | git add . 76 | git commit -m "Switch to using tfe_outputs" 77 | git push --set-upstream origin tfe-outputs 78 | 79 | # Go through the code deployment process 80 | # Update network workspace state sharing 81 | 82 | # Go to application config directory 83 | cd ../application_config 84 | 85 | # Create a new branch called tfe-outputs 86 | git checkout -b tfe-outputs 87 | 88 | # Make code changes 89 | # Commit code changes to branch 90 | git add . 91 | git commit -m "Switch to using tfe_outputs" 92 | git push --set-upstream origin tfe-outputs 93 | 94 | # Add new variable values to application workspace 95 | # Create pull request and merge once there are no changes 96 | 97 | # Merge the changes back to the main branch in GitHub too 98 | -------------------------------------------------------------------------------- /m9/application_config_complete/resources.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # PROVIDERS 3 | ################################################################################## 4 | 5 | provider "aws" { 6 | region = var.region 7 | } 8 | 9 | ################################################################################## 10 | # LOCALS 11 | ################################################################################## 12 | 13 | locals { 14 | 15 | common_tags = { 16 | Environment = var.environment 17 | BillingCode = var.billing_code 18 | } 19 | 20 | name_prefix = "${var.prefix}-${var.environment}" 21 | 22 | } 23 | 24 | ################################################################################## 25 | # RESOURCES 26 | ################################################################################## 27 | resource "aws_iam_instance_profile" "main" { 28 | name = "${local.name_prefix}-webapp" 29 | role = var.ec2_role_name 30 | 31 | tags = local.common_tags 32 | } 33 | 34 | resource "aws_instance" "main" { 35 | count = length(data.tfe_outputs.networking.nonsensitive_values.public_subnets) 36 | ami = nonsensitive(data.aws_ssm_parameter.amzn2_linux.value) 37 | instance_type = var.instance_type 38 | subnet_id = data.tfe_outputs.networking.nonsensitive_values.public_subnets[count.index] 39 | vpc_security_group_ids = [ 40 | aws_security_group.webapp_http_inbound_sg.id, 41 | aws_security_group.webapp_ssh_inbound_sg.id, 42 | aws_security_group.webapp_outbound_sg.id, 43 | ] 44 | 45 | key_name = module.ssh_keys.key_pair_name 46 | 47 | tags = merge(local.common_tags, { 48 | "Name" = "${local.name_prefix}-webapp-${count.index}" 49 | }) 50 | 51 | user_data = templatefile("${path.module}/templates/userdata.sh", { 52 | playbook_repository = var.playbook_repository 53 | secret_id = var.api_key_secret_id 54 | host_list_ssm_name = local.host_list_ssm_name 55 | site_name_ssm_name = local.site_name_ssm_name 56 | }) 57 | 58 | user_data_replace_on_change = true 59 | iam_instance_profile = aws_iam_instance_profile.main.name 60 | 61 | } 62 | 63 | resource "aws_lb" "main" { 64 | name = "${local.name_prefix}-webapp" 65 | internal = false 66 | load_balancer_type = "application" 67 | security_groups = [aws_security_group.webapp_http_inbound_sg.id] 68 | subnets = data.tfe_outputs.networking.nonsensitive_values.public_subnets 69 | 70 | enable_deletion_protection = false 71 | 72 | tags = local.common_tags 73 | } 74 | 75 | resource "aws_lb_listener" "main" { 76 | load_balancer_arn = aws_lb.main.arn 77 | port = "80" 78 | protocol = "HTTP" 79 | 80 | default_action { 81 | type = "forward" 82 | target_group_arn = aws_lb_target_group.main.arn 83 | } 84 | } 85 | 86 | resource "aws_lb_target_group" "main" { 87 | name = "${local.name_prefix}-webapp" 88 | port = 80 89 | target_type = "instance" 90 | protocol = "HTTP" 91 | vpc_id = data.tfe_outputs.networking.nonsensitive_values.vpc_id 92 | } 93 | 94 | resource "aws_alb_target_group_attachment" "main" { 95 | count = length(aws_instance.main.*.id) 96 | target_group_arn = aws_lb_target_group.main.arn 97 | target_id = aws_instance.main[count.index].id 98 | } -------------------------------------------------------------------------------- /m7/application_config_example/resources.tf: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # PROVIDERS 3 | ################################################################################## 4 | 5 | provider "aws" { 6 | region = var.region 7 | } 8 | 9 | ################################################################################## 10 | # LOCALS 11 | ################################################################################## 12 | 13 | locals { 14 | 15 | common_tags = { 16 | Environment = var.environment 17 | BillingCode = var.billing_code 18 | } 19 | 20 | name_prefix = "${var.prefix}-${var.environment}" 21 | 22 | } 23 | 24 | ################################################################################## 25 | # RESOURCES 26 | ################################################################################## 27 | 28 | resource "aws_instance" "main" { 29 | count = length(var.public_subnets) 30 | ami = nonsensitive(data.aws_ssm_parameter.amzn2_linux.value) 31 | instance_type = var.instance_type 32 | subnet_id = var.public_subnets[count.index] 33 | vpc_security_group_ids = [ 34 | aws_security_group.webapp_http_inbound_sg.id, 35 | aws_security_group.webapp_ssh_inbound_sg.id, 36 | aws_security_group.webapp_outbound_sg.id, 37 | ] 38 | 39 | key_name = module.ssh_keys.key_pair_name 40 | 41 | tags = merge(local.common_tags, { 42 | "Name" = "${local.name_prefix}-webapp-${count.index}" 43 | }) 44 | 45 | # Provisioner Stuff 46 | connection { 47 | type = "ssh" 48 | user = "ec2-user" 49 | port = "22" 50 | host = self.public_ip 51 | private_key = module.ssh_keys.private_key_openssh 52 | } 53 | 54 | provisioner "file" { 55 | source = "./templates/userdata.sh" 56 | destination = "/home/ec2-user/userdata.sh" 57 | } 58 | 59 | provisioner "remote-exec" { 60 | inline = [ 61 | "chmod +x /home/ec2-user/userdata.sh", 62 | "sh /home/ec2-user/userdata.sh", 63 | ] 64 | on_failure = continue 65 | } 66 | 67 | } 68 | 69 | resource "null_resource" "webapp" { 70 | 71 | triggers = { 72 | webapp_server_count = length(aws_instance.main.*.id) 73 | web_server_names = join(",", aws_instance.main.*.id) 74 | } 75 | 76 | provisioner "file" { 77 | content = templatefile("./templates/application.config.tpl", { 78 | hosts = aws_instance.main.*.private_dns 79 | site_name = "${local.name_prefix}-taco-wagon" 80 | api_key = var.api_key 81 | }) 82 | destination = "/home/ec2-user/application.config" 83 | } 84 | 85 | connection { 86 | type = "ssh" 87 | user = "ec2-user" 88 | port = "22" 89 | host = aws_instance.main[0].public_ip 90 | private_key = module.ssh_keys.private_key_openssh 91 | } 92 | 93 | } 94 | 95 | resource "aws_lb" "main" { 96 | name = "${local.name_prefix}-webapp" 97 | internal = false 98 | load_balancer_type = "application" 99 | security_groups = [aws_security_group.webapp_http_inbound_sg.id] 100 | subnets = var.public_subnets 101 | 102 | enable_deletion_protection = false 103 | 104 | tags = local.common_tags 105 | } 106 | 107 | resource "aws_lb_listener" "main" { 108 | load_balancer_arn = aws_lb.main.arn 109 | port = "80" 110 | protocol = "HTTP" 111 | 112 | default_action { 113 | type = "forward" 114 | target_group_arn = aws_lb_target_group.main.arn 115 | } 116 | } 117 | 118 | resource "aws_lb_target_group" "main" { 119 | name = "${local.name_prefix}-webapp" 120 | port = 80 121 | target_type = "instance" 122 | protocol = "HTTP" 123 | vpc_id = var.vpc_id 124 | } 125 | 126 | resource "aws_alb_target_group_attachment" "main" { 127 | count = length(aws_instance.main.*.id) 128 | target_group_arn = aws_lb_target_group.main.arn 129 | target_id = aws_instance.main[count.index].id 130 | } -------------------------------------------------------------------------------- /m3/cloud_formation_template/vpc_template.yaml: -------------------------------------------------------------------------------- 1 | Description: This template deploys a VPC, with a pair of public subnets spread 2 | across two Availability Zones. It deploys an internet gateway, with a default 3 | route on the public subnets. 4 | 5 | Parameters: 6 | EnvironmentName: 7 | Description: An environment name that is prefixed to resource names 8 | Type: String 9 | 10 | VpcCIDR: 11 | Description: Please enter the IP range (CIDR notation) for this VPC 12 | Type: String 13 | Default: 10.42.0.0/16 14 | 15 | PublicSubnet1CIDR: 16 | Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone 17 | Type: String 18 | Default: 10.42.10.0/24 19 | 20 | PublicSubnet2CIDR: 21 | Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone 22 | Type: String 23 | Default: 10.42.11.0/24 24 | 25 | Resources: 26 | VPC: 27 | Type: AWS::EC2::VPC 28 | Properties: 29 | CidrBlock: !Ref VpcCIDR 30 | EnableDnsSupport: true 31 | EnableDnsHostnames: true 32 | Tags: 33 | - Key: Name 34 | Value: !Ref EnvironmentName 35 | 36 | InternetGateway: 37 | Type: AWS::EC2::InternetGateway 38 | Properties: 39 | Tags: 40 | - Key: Name 41 | Value: !Ref EnvironmentName 42 | 43 | InternetGatewayAttachment: 44 | Type: AWS::EC2::VPCGatewayAttachment 45 | Properties: 46 | InternetGatewayId: !Ref InternetGateway 47 | VpcId: !Ref VPC 48 | 49 | PublicSubnet1: 50 | Type: AWS::EC2::Subnet 51 | Properties: 52 | VpcId: !Ref VPC 53 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 54 | CidrBlock: !Ref PublicSubnet1CIDR 55 | MapPublicIpOnLaunch: true 56 | Tags: 57 | - Key: Name 58 | Value: !Sub ${EnvironmentName}-public-1 59 | 60 | PublicSubnet2: 61 | Type: AWS::EC2::Subnet 62 | Properties: 63 | VpcId: !Ref VPC 64 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 65 | CidrBlock: !Ref PublicSubnet2CIDR 66 | MapPublicIpOnLaunch: true 67 | Tags: 68 | - Key: Name 69 | Value: !Sub ${EnvironmentName}-public-2 70 | 71 | PublicRouteTable: 72 | Type: AWS::EC2::RouteTable 73 | Properties: 74 | VpcId: !Ref VPC 75 | Tags: 76 | - Key: Name 77 | Value: !Sub ${EnvironmentName}-public 78 | 79 | DefaultPublicRoute: 80 | Type: AWS::EC2::Route 81 | DependsOn: InternetGatewayAttachment 82 | Properties: 83 | RouteTableId: !Ref PublicRouteTable 84 | DestinationCidrBlock: 0.0.0.0/0 85 | GatewayId: !Ref InternetGateway 86 | 87 | PublicSubnet1RouteTableAssociation: 88 | Type: AWS::EC2::SubnetRouteTableAssociation 89 | Properties: 90 | RouteTableId: !Ref PublicRouteTable 91 | SubnetId: !Ref PublicSubnet1 92 | 93 | PublicSubnet2RouteTableAssociation: 94 | Type: AWS::EC2::SubnetRouteTableAssociation 95 | Properties: 96 | RouteTableId: !Ref PublicRouteTable 97 | SubnetId: !Ref PublicSubnet2 98 | 99 | NoIngressSecurityGroup: 100 | Type: AWS::EC2::SecurityGroup 101 | Properties: 102 | GroupName: "no-ingress-sg" 103 | GroupDescription: "Security group with no ingress rule" 104 | VpcId: !Ref VPC 105 | 106 | Outputs: 107 | VPC: 108 | Description: A reference to the created VPC 109 | Value: !Ref VPC 110 | 111 | PublicSubnet1: 112 | Description: A reference to the public subnet in the 1st Availability Zone 113 | Value: !Ref PublicSubnet1 114 | 115 | PublicSubnet2: 116 | Description: A reference to the public subnet in the 2nd Availability Zone 117 | Value: !Ref PublicSubnet2 118 | 119 | NoIngressSecurityGroup: 120 | Description: Security group with no ingress rule 121 | Value: !Ref NoIngressSecurityGroup 122 | 123 | InternetGateway: 124 | Description: InternetGateway reference 125 | Value: !Ref InternetGateway 126 | 127 | PublicRouteTable: 128 | Description: Route table for the public subnets 129 | Value: !Ref PublicRouteTable 130 | 131 | DefaultPublicRoute: 132 | Description: Default public route for public subnet route table 133 | Value: !Sub '${PublicRouteTable}_0.0.0.0/0' 134 | 135 | PublicSubnet1RouteTableAssociation: 136 | Description: Route table associate for first public subnet 137 | Value: !Sub '${PublicSubnet1}/${PublicRouteTable}' 138 | 139 | PublicSubnet2RouteTableAssociation: 140 | Description: Route table associate for second public subnet 141 | Value: !Sub '${PublicSubnet2}/${PublicRouteTable}' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep-Dive-Terraform 2 | 3 | Welcome to Terraform - Deep Dive version 3. These exercise files are meant to accompany my course on [Pluralsight](https://app.pluralsight.com/library/courses/terraform-deep-dive-2023). The course was developed using version 1.5.x of Terraform. 4 | 5 | If you're looking for the older versions of the course, that is still available on the v1 and v2 branches. I am no longer maintaining them, but I thought I would keep them around for posterity or if you're already working through those versions of the course. 6 | 7 | ## AWS Account 8 | 9 | You are going to need an account where you have FullAdmin permissions. You are going to be creating policies, roles, profiles, VPCs, etc. If you don't have enough permissions in your current environment, then I recommend creating a temporary account to mess around in. In fact, probably do that regardless. You don't want to accidentally mess something up at work because you were trying to learn about Terraform. 10 | 11 | You may exceed your EIP address quota when deploying multiple enviornments. You can request an increase through the AWS console in the Services Quotas area, under the Amazon Elastic Compute Cloud category. I recommend setting it to 15 just to be safe. It should be approved almost immediately, but may take 30 minutes to apply. So if you do it now, it should be ready long before you get to that portion of the course. 12 | 13 | ## Using the files 14 | 15 | Each folder represents a module from the course and they all build off of each other. You will be creating two working directories in your local copy of the exercise files: `network_config` and `application_config`. Both of these folders are part of the `.gitignore` so they will not be committed back to the repository. 16 | 17 | In the module folders, I have included Terraform configurations to help set up the necessary prerequisites. I've also included the commands you should run if you're following along. You don't have to run them verbatim, and I encourage you to mess around and try out different flags and commands. If you run into an issue, please submit it as such and I will do my best to remediate it. 18 | 19 | ## Line Endings 20 | 21 | An issue I have discovered from time to time is that Terraform doesn't much like the Windows style of ending a line with both a Carriage Return (CR) and a Line Feed (LF), commonly referred to as CRLF. If you are experiencing strange parsing issues, change the line ending to be Line Feed (LF) only. In VS Code this can be down by clicking on the CRLF in the lower right corner and changing it to LF. 22 | 23 | ## MONEY!!! 24 | 25 | A gentle reminder about cost. The course will have you creating resources in AWS. Some of the resources are not going to be 100% free. In most cases I have tried to use the [Free-tier](https://aws.amazon.com/free/) when possible, but AWS is about to start charging for IPv4 addresses even when they're attached to a resource. I'm not sure how this will impact the free tier, so just be cognizant of what you're creating. 26 | 27 | When you complete an exercise in the course, you can always run `terraform destroy` and approve the destruction to remove all resources from AWS. Once you get to the application deployment, you will need to leave the networking in place for a successful application deployment. If you wish to tear things down, destroy the application first and then the network. 28 | 29 | ## Terraform Cloud and GitHub Actions 30 | 31 | Previous versions of this course used Docker, Consul, and Jenkins to provide remote state and CI/CD operations. This ended up being a sticking point for several people, and they ended up abandoning the course. To simplify things, I have replaced those technologies with Terraform Cloud and GitHub Actions. This is not an overt endorsement of either technology, there are plenty of other great options when it comes to remote state storage and CI/CD for IaC. I chose GitHub Actions because we are already using GitHub any way, and I chose Terraform Cloud because it has a solid free tier and it is part of the Terraform Associate certification. Speaking of which... 32 | 33 | ## Certification 34 | 35 | HashiCorp has released the *Terraform Certified Associate* certification. You might be wondering if this course fully prepares you for the cert. **It does not.** Taking this course along with the [Terraform - Getting Started](https://app.pluralsight.com/library/courses/terraform-getting-started-2023) course on Pluralsight will meet all of the learning objectives for the certification, but there is no substitute for running the software on your own and hacking away. 36 | 37 | I have coauthored a certification guide with Adin Ermie which you can find on [Leanpub](https://leanpub.com/terraform-certified/). This is an unofficial guide, but I believe in concert with the Pluralsight courses you will be in a good position to sit the exam. 38 | 39 | ## Conclusion 40 | 41 | I hope you enjoy taking this course as much as I did creating it. I'd love to hear feedback and suggestions for revisions. 42 | 43 | Thanks and happy automating! 44 | 45 | Ned 46 | --------------------------------------------------------------------------------