├── .github └── FUNDING.yml ├── labs ├── lab_15_build_your_own_modules │ ├── AZURE │ │ ├── variables.tf │ │ ├── outputs.tf │ │ ├── main.tf │ │ └── providers.tf │ ├── AWS │ │ ├── outputs.tf │ │ ├── main.tf │ │ ├── providers.tf │ │ └── variables.tf │ └── GITHUB │ │ ├── outputs.tf │ │ ├── main.tf │ │ ├── variables.tf │ │ └── providers.tf ├── lab_08_create_mulitple_resources_with_count │ ├── GITHUB │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf │ ├── AWS │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf │ └── AZURE │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf ├── lab_06_making_code_dynamic_and_reusable │ ├── AWS │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf │ ├── GITHUB │ │ ├── variables.tf │ │ ├── providers.tf │ │ ├── main.tf │ │ └── github.md │ └── AZURE │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf ├── lab_14_using_the_terraform_registry │ ├── AWS │ │ ├── main.tf │ │ ├── providers.tf │ │ └── variables.tf │ ├── GITHUB │ │ ├── main.tf │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── github.md │ └── AZURE │ │ ├── main.tf │ │ ├── providers.tf │ │ └── variables.tf ├── lab_13_using_terraform_built_in_functions │ ├── AWS │ │ ├── main.tf │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── aws.md │ ├── AZURE │ │ ├── main.tf │ │ ├── variables.tf │ │ └── providers.tf │ └── GITHUB │ │ ├── main.tf │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── github.md ├── lab_07_simplify_code_with_local_values │ ├── GITHUB │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf │ ├── AWS │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf │ └── AZURE │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf ├── lab_11_using_multiple_providers_for_mulitple_regions │ ├── GITHUB │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf │ ├── AZURE │ │ ├── providers.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── azure.md │ └── AWS │ │ ├── variables.tf │ │ ├── providers.tf │ │ ├── main.tf │ │ └── aws.md ├── lab_09_deploying_mulitple_resources_with_for_each │ ├── GITHUB │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf │ ├── AWS │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf │ └── AZURE │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf ├── lab_10_managing_explicit_dependencies_with_depends_on │ ├── GITHUB │ │ ├── providers.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── github.md │ ├── AZURE │ │ ├── providers.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── azure.md │ └── AWS │ │ ├── providers.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── aws.md ├── lab_12_managing_resource_lifecycle_using_lifecyle │ ├── GITHUB │ │ ├── providers.tf │ │ ├── variables.tf │ │ └── main.tf │ ├── AZURE │ │ ├── providers.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── azure.md │ └── AWS │ │ ├── variables.tf │ │ ├── providers.tf │ │ └── main.tf ├── lab_16_replace_a_single_resource │ ├── GITHUB │ │ ├── providers.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── github.md │ ├── AWS │ │ ├── providers.tf │ │ ├── outputs.tf │ │ ├── main.tf │ │ └── variables.tf │ └── AZURE │ │ ├── providers.tf │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── azure.md ├── lab_01_getting_started_with_terraform │ ├── github.md │ ├── azure.md │ └── aws.md ├── lab_04_managing_mulitple_resources │ └── github.md ├── lab_03_working_with_variables_and_dependencies │ └── azure.md ├── lab_02_create_your_first_resource │ ├── aws.md │ └── github.md └── lab_05_working_with_state_data_sources_and_cli │ └── github.md ├── .devcontainer ├── devcontainer.json └── Dockerfile └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [btkrausen] 2 | -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AWS/outputs.tf: -------------------------------------------------------------------------------- 1 | # Add outputs below -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Add resource blocks below -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AZURE/outputs.tf: -------------------------------------------------------------------------------- 1 | # Add outputs below 2 | -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/GITHUB/outputs.tf: -------------------------------------------------------------------------------- 1 | # Add outputs below 2 | -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | # Add variables below -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Add resource blocks below -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | # Add variables below -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Add resource blocks below -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | # Add variables below 2 | -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | 3 | -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | 3 | -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Add resources for the lab below 2 | -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Deployment environment" 3 | type = string 4 | default = "dev" 5 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Environment name for resource naming" 3 | type = string 4 | default = "production" 5 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "Azure region to deploy resources" 3 | type = string 4 | default = "eastus" 5 | } 6 | -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "repo_name" { 2 | description = "Name for the repository" 3 | type = string 4 | default = "terraform-provider-demo-repo" 5 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 5.0" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 6.5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 6.5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = ">= 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 6.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = ">= 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = "~> 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = "us-east-1" 13 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = "~> 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = ">= 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = ">= 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | } 9 | } 10 | 11 | provider "aws" { 12 | region = var.region 13 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = ">= 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "Azure location" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } -------------------------------------------------------------------------------- /labs/lab_15_build_your_own_modules/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "environment" { 8 | description = "Deployment environment" 9 | type = string 10 | default = "dev" 11 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Environment name" 3 | type = string 4 | default = "dev" 5 | } 6 | 7 | variable "repo_visibility" { 8 | description = "Repository visibility" 9 | type = string 10 | default = "public" 11 | } -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "The Azure region to deploy resources" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Environment name (e.g., dev, prod)" 3 | type = string 4 | default = "prod" 5 | } 6 | 7 | variable "location" { 8 | description = "Azure region for all resources" 9 | type = string 10 | default = "East US" 11 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/GITHUB/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | github = { 5 | source = "integrations/github" 6 | version = "~> 6.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "github" {} -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = ">= 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | region = var.region 17 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = "~> 5.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | region = var.aws_region 17 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features {} 17 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = ">= 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | region = var.region 17 | } -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = ">= 4.0.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = ">= 3.0.0" 11 | } 12 | } 13 | } 14 | 15 | provider "azurerm" { 16 | features {} 17 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0.0" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = ">= 3.0.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | region = "us-east-1" 17 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AWS/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_name" { 2 | description = "Name of the created S3 bucket" 3 | value = aws_s3_bucket.example.bucket 4 | } 5 | 6 | output "role_name" { 7 | description = "Name of the created IAM role" 8 | value = aws_iam_role.example.name 9 | } 10 | 11 | output "policy_name" { 12 | description = "Name of the created IAM policy" 13 | value = aws_iam_policy.example.name 14 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AZURE/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "~> 4.0" 7 | } 8 | } 9 | } 10 | 11 | # Primary provider 12 | provider "azurerm" { 13 | features {} 14 | alias = "primary" 15 | } 16 | 17 | # Secondary provider 18 | provider "azurerm" { 19 | features {} 20 | alias = "secondary" 21 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "primary_region" { 2 | description = "Primary AWS region" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "secondary_region" { 8 | description = "Secondary AWS region" 9 | type = string 10 | default = "us-west-2" 11 | } 12 | 13 | variable "environment" { 14 | description = "Environment name" 15 | type = string 16 | default = "dev" 17 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Environment name" 3 | type = string 4 | default = "dev" 5 | } 6 | 7 | variable "repo_visibility" { 8 | description = "Repository visibility" 9 | type = string 10 | default = "public" 11 | } 12 | 13 | variable "default_branch" { 14 | description = "Default repository branch" 15 | type = string 16 | default = "main" 17 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/GITHUB/outputs.tf: -------------------------------------------------------------------------------- 1 | output "repository_name" { 2 | description = "Name of the created repository" 3 | value = github_repository.example.name 4 | } 5 | 6 | output "repository_url" { 7 | description = "URL of the created repository" 8 | value = github_repository.example.html_url 9 | } 10 | 11 | output "development_branch" { 12 | description = "Name of the development branch" 13 | value = github_branch.development.branch 14 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "primary_location" { 2 | description = "Primary Azure location" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "secondary_location" { 8 | description = "Secondary Azure location" 9 | type = string 10 | default = "westus" 11 | } 12 | 13 | variable "environment" { 14 | description = "Environment name" 15 | type = string 16 | default = "dev" 17 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AWS/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.10.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0" 7 | } 8 | } 9 | } 10 | 11 | # Primary region provider 12 | provider "aws" { 13 | region = var.primary_region 14 | alias = "primary" 15 | } 16 | 17 | # Secondary region provider 18 | provider "aws" { 19 | region = var.secondary_region 20 | alias = "secondary" 21 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name for resource naming and tagging" 9 | type = string 10 | default = "production" 11 | } 12 | 13 | variable "vpc_cidr" { 14 | description = "CIDR block for VPC" 15 | type = string 16 | default = "10.0.0.0/16" 17 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "Azure region to deploy resources" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name for resource naming and tagging" 9 | type = string 10 | default = "production" 11 | } 12 | 13 | variable "vnet_address_space" { 14 | description = "Address space for Virtual Network" 15 | type = list(string) 16 | default = ["10.0.0.0/16"] 17 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "repo_names" { 2 | description = "Names for repositories" 3 | type = list(string) 4 | default = ["repo-1", "repo-2", "repo-3"] 5 | } 6 | 7 | variable "label_names" { 8 | description = "Names for issue labels" 9 | type = list(string) 10 | default = ["bug", "feature"] 11 | } 12 | 13 | variable "label_colors" { 14 | description = "Colors for issue labels" 15 | type = list(string) 16 | default = ["FF0000", "00FF00"] 17 | } -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with hardcoded values 2 | resource "github_repository" "production" { 3 | name = "production-application" 4 | description = "Production application repository" 5 | visibility = "public" 6 | 7 | has_issues = true 8 | has_wiki = true 9 | has_discussions = true 10 | 11 | allow_merge_commit = true 12 | allow_rebase_merge = true 13 | allow_squash_merge = true 14 | 15 | topics = [ 16 | "production", 17 | "application", 18 | "infrastructure" 19 | ] 20 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "vpc_cidr" { 8 | description = "VPC CIDR block" 9 | type = string 10 | default = "10.0.0.0/16" 11 | } 12 | 13 | variable "subnet_cidr" { 14 | description = "Subnet CIDR block" 15 | type = string 16 | default = "10.0.1.0/24" 17 | } 18 | 19 | variable "environment" { 20 | description = "Environment name" 21 | type = string 22 | default = "dev" 23 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "Azure region" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } 12 | 13 | variable "vnet_address_space" { 14 | description = "Address space for Virtual Network" 15 | type = list(string) 16 | default = ["10.0.0.0/16"] 17 | } 18 | 19 | variable "subnet_address_prefix" { 20 | description = "Address prefix for subnet" 21 | type = list(string) 22 | default = ["10.0.1.0/24"] 23 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Standard repository without lifecycle configuration 2 | resource "github_repository" "standard" { 3 | name = "standard-${var.environment}-repo" 4 | description = "Standard repository for ${var.environment} environment" 5 | visibility = var.repo_visibility 6 | auto_init = true 7 | 8 | topics = ["terraform", "lifecycle-demo"] 9 | } 10 | 11 | # Issue label without lifecycle configuration 12 | resource "github_issue_label" "standard" { 13 | repository = github_repository.standard.name 14 | name = "standard" 15 | color = "FF0000" 16 | description = "Standard issue label" 17 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Terraform Environment", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | }, 6 | "customizations": { 7 | "vscode": { 8 | "extensions": [ 9 | "hashicorp.terraform", 10 | "github.vscode-pull-request-github" 11 | ], 12 | "settings": { 13 | "terminal.integrated.defaultProfile.linux": "zsh", 14 | "workbench.editorAssociations": { 15 | "*.md": "vscode.markdown.preview" 16 | } 17 | } 18 | } 19 | }, 20 | "remoteUser": "root", 21 | "postCreateCommand": "terraform --version && git --version" 22 | } -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "repository_visibility" { 2 | description = "Repo visibility configuration" 3 | type = string 4 | default = "public" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "development" 11 | } 12 | 13 | variable "user_repos" { 14 | description = "Map of users" 15 | type = map(string) 16 | default = { 17 | user1 = "kristen" 18 | user2 = "aaron" 19 | user3 = "jack" 20 | user4 = "frank" 21 | user5 = "monica" 22 | } 23 | } 24 | 25 | variable "repo_org" { 26 | description = "Organization name" 27 | type = string 28 | default = "my-org" 29 | } -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "Environment name" 3 | type = string 4 | default = "dev" 5 | } 6 | 7 | variable "repository_names" { 8 | description = "List of repository name suffixes" 9 | type = list(string) 10 | default = ["api", "web", "docs", "utils", "cli"] 11 | } 12 | 13 | variable "team_members" { 14 | description = "List of team members with duplicates" 15 | type = list(string) 16 | default = ["user1", "user2", "user3", "user1", "user4"] 17 | } 18 | 19 | variable "topics" { 20 | description = "List of repository topics" 21 | type = list(string) 22 | default = ["terraform", "infrastructure", "devops", "automation"] 23 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vnet_cidr_blocks" { 2 | description = "CIDR blocks for virtual networks" 3 | type = list(string) 4 | default = ["10.0.0.0/16", "10.1.0.0/16", "10.2.0.0/16"] 5 | } 6 | 7 | variable "subnet_prefixes" { 8 | description = "Address prefixes for subnets" 9 | type = list(string) 10 | default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] 11 | } 12 | 13 | variable "nsg_names" { 14 | description = "Names for network security groups" 15 | type = list(string) 16 | default = ["web", "app", "db"] 17 | } 18 | 19 | variable "nsg_ports" { 20 | description = "Ports for network security groups" 21 | type = list(number) 22 | default = [80, 8080, 3306] 23 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Repository for primary owner 2 | resource "github_repository" "primary" { 3 | provider = github.primary 4 | name = "terraform-${var.environment}-primary" 5 | description = "Terraform managed repository for ${var.environment} environment" 6 | visibility = var.repo_visibility 7 | auto_init = true 8 | 9 | topics = ["terraform", "multi-provider-demo"] 10 | } 11 | 12 | # Repository for secondary owner 13 | resource "github_repository" "secondary" { 14 | provider = github.secondary 15 | name = "terraform-${var.environment}-secondary" 16 | description = "Terraform managed repository for ${var.environment} environment" 17 | visibility = var.repo_visibility 18 | auto_init = true 19 | 20 | topics = ["terraform", "multi-provider-demo"] 21 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AZURE/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resource_group_name" { 2 | description = "Name of the created resource group" 3 | value = azurerm_resource_group.example.name 4 | } 5 | 6 | output "storage_account_name" { 7 | description = "Name of the created storage account" 8 | value = azurerm_storage_account.example.name 9 | } 10 | 11 | output "container_name" { 12 | description = "Name of the created storage container" 13 | value = azurerm_storage_container.example.name 14 | } 15 | 16 | output "user_assigned_identity_id" { 17 | description = "ID of the created user-assigned managed identity" 18 | value = azurerm_user_assigned_identity.example.id 19 | } 20 | 21 | output "policy_definition_id" { 22 | description = "ID of the created policy definition" 23 | value = azurerm_policy_definition.example.id 24 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group without lifecycle configuration 2 | resource "azurerm_resource_group" "standard" { 3 | name = "rg-standard-${var.environment}" 4 | location = var.location 5 | 6 | tags = { 7 | Environment = var.environment 8 | Purpose = "Standard" 9 | } 10 | } 11 | 12 | # Storage Account without lifecycle configuration 13 | resource "azurerm_storage_account" "standard" { 14 | name = "standardsa${formatdate("YYMMDD", timestamp())}" 15 | resource_group_name = azurerm_resource_group.standard.name 16 | location = azurerm_resource_group.standard.location 17 | account_tier = "Standard" 18 | account_replication_type = "LRS" 19 | 20 | tags = { 21 | Environment = var.environment 22 | Purpose = "Standard" 23 | } 24 | } -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } 12 | 13 | variable "vpc_cidr" { 14 | description = "CIDR block for VPC" 15 | type = string 16 | default = "10.0.0.0/16" 17 | } 18 | 19 | variable "vpc_name" { 20 | description = "Name of VPC" 21 | type = string 22 | default = "main-vpc" 23 | } 24 | 25 | variable "s3_buckets" { 26 | description = "Map of S3 buckets to create" 27 | type = map(string) 28 | default = { 29 | "logs" = "Stores application logs" 30 | "artifacts" = "Stores build artifacts" 31 | "configs" = "Stores application configs" 32 | } 33 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "subnet_cidr_blocks" { 8 | description = "CIDR blocks for subnets" 9 | type = list(string) 10 | default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] 11 | } 12 | 13 | variable "availability_zones" { 14 | description = "Availability zones for subnets" 15 | type = list(string) 16 | default = ["us-east-1a", "us-east-1b", "us-east-1c"] 17 | } 18 | 19 | variable "security_groups" { 20 | description = "Security group names" 21 | type = list(string) 22 | default = ["web", "app", "db"] 23 | } 24 | 25 | variable "sg_ports" { 26 | description = "Ports for security group rules" 27 | type = list(number) 28 | default = [80, 8080, 3306] 29 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # S3 Bucket in primary region 2 | resource "aws_s3_bucket" "primary" { 3 | provider = aws.primary 4 | bucket = "primary-${var.environment}-${random_string.suffix.result}" 5 | 6 | tags = { 7 | Name = "Primary Region Bucket" 8 | Environment = var.environment 9 | Region = var.primary_region 10 | } 11 | } 12 | 13 | # S3 Bucket in secondary region 14 | resource "aws_s3_bucket" "secondary" { 15 | provider = aws.secondary 16 | bucket = "secondary-${var.environment}-${random_string.suffix.result}" 17 | 18 | tags = { 19 | Name = "Secondary Region Bucket" 20 | Environment = var.environment 21 | Region = var.secondary_region 22 | } 23 | } 24 | 25 | # Random string for bucket name uniqueness 26 | resource "random_string" "suffix" { 27 | length = 8 28 | special = false 29 | upper = false 30 | } -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Random string for uniqueness 2 | resource "random_string" "suffix" { 3 | length = 8 4 | special = false 5 | upper = false 6 | } 7 | 8 | # S3 Bucket without lifecycle configuration 9 | resource "aws_s3_bucket" "standard" { 10 | bucket = "standard-${var.environment}-${random_string.suffix.result}" 11 | 12 | tags = { 13 | Name = "Standard Bucket" 14 | Environment = var.environment 15 | } 16 | } 17 | 18 | # DynamoDB Table without lifecycle configuration 19 | resource "aws_dynamodb_table" "standard" { 20 | name = "standard-${var.environment}-${random_string.suffix.result}" 21 | billing_mode = "PAY_PER_REQUEST" 22 | hash_key = "Id" 23 | 24 | attribute { 25 | name = "Id" 26 | type = "S" 27 | } 28 | 29 | tags = { 30 | Name = "Standard Table" 31 | Environment = var.environment 32 | } 33 | } -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "environment" { 8 | description = "Environment name" 9 | type = string 10 | default = "dev" 11 | } 12 | 13 | variable "subnet_cidrs" { 14 | description = "List of subnet CIDR blocks" 15 | type = list(string) 16 | default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"] 17 | } 18 | 19 | variable "availability_zones" { 20 | description = "List of availability zones" 21 | type = list(string) 22 | default = ["us-east-1a", "us-east-1b", "us-east-1c"] 23 | } 24 | 25 | variable "teams" { 26 | description = "List of teams with duplicates" 27 | type = list(string) 28 | default = ["development", "operations", "security", "development"] 29 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Base Repository 2 | resource "github_repository" "main" { 3 | name = "terraform-${var.environment}-repo" 4 | description = "Terraform managed ${var.environment} repository" 5 | visibility = var.repo_visibility 6 | auto_init = true 7 | 8 | topics = ["terraform", "depends-on-demo"] 9 | } 10 | 11 | # Repository File 12 | resource "github_repository_file" "readme" { 13 | repository = github_repository.main.name 14 | branch = var.default_branch 15 | file = "README.md" 16 | content = "# Terraform ${var.environment} Repository\n\nManaged by Terraform." 17 | commit_message = "Add README" 18 | commit_author = "Terraform" 19 | commit_email = "terraform@example.com" 20 | overwrite_on_create = true 21 | } 22 | 23 | # Issue Label 24 | resource "github_issue_label" "bug" { 25 | repository = github_repository.main.name 26 | name = "bug" 27 | color = "FF0000" 28 | description = "Bug reports" 29 | } -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with hardcoded values 2 | resource "azurerm_resource_group" "production" { 3 | name = "production-resources" 4 | location = "eastus" 5 | 6 | tags = { 7 | Environment = "production" 8 | Project = "static-infrastructure" 9 | ManagedBy = "manual-deployment" 10 | Region = "eastus" 11 | } 12 | } 13 | 14 | resource "azurerm_virtual_network" "production" { 15 | name = "production-network" 16 | resource_group_name = azurerm_resource_group.production.name 17 | location = azurerm_resource_group.production.location 18 | address_space = ["10.0.0.0/16"] 19 | 20 | tags = { 21 | Environment = "production" 22 | Project = "static-infrastructure" 23 | ManagedBy = "manual-deployment" 24 | Region = "eastus" 25 | } 26 | } 27 | 28 | resource "azurerm_subnet" "private" { 29 | name = "production-subnet" 30 | resource_group_name = azurerm_resource_group.production.name 31 | virtual_network_name = azurerm_virtual_network.production.name 32 | address_prefixes = ["10.0.1.0/24"] 33 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Repositories created with count 2 | resource "github_repository" "repo_count" { 3 | count = 3 4 | name = "repo-count-${count.index + 1}" 5 | description = "Repository ${count.index + 1} created with count" 6 | visibility = "public" 7 | auto_init = true 8 | 9 | topics = ["terraform", "count", "example"] 10 | } 11 | 12 | # Branch protection rules created with count 13 | resource "github_branch_protection" "protection_count" { 14 | count = 3 15 | repository_id = github_repository.repo_count[count.index].node_id 16 | pattern = "main" 17 | 18 | allows_deletions = false 19 | allows_force_pushes = false 20 | require_conversation_resolution = true 21 | } 22 | 23 | # Issue labels created with count 24 | resource "github_issue_label" "label_count" { 25 | count = 6 # 2 labels for each of 3 repos 26 | repository = github_repository.repo_count[count.index % 3].name 27 | name = count.index < 3 ? "bug" : "feature" 28 | color = count.index < 3 ? "FF0000" : "00FF00" 29 | description = count.index < 3 ? "Bug issues" : "Feature requests" 30 | } -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rockylinux:9-minimal 2 | 3 | # Install minimal required packages 4 | RUN microdnf install -y \ 5 | unzip \ 6 | git \ 7 | curl \ 8 | jq \ 9 | && microdnf clean all 10 | 11 | # Install AWS CLI v2 (binary version) 12 | RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ 13 | unzip awscliv2.zip && \ 14 | ./aws/install && \ 15 | rm -rf aws awscliv2.zip 16 | 17 | # Install Azure CLI (RPM direct installation) 18 | RUN microdnf install -y dnf && \ 19 | rpm --import https://packages.microsoft.com/keys/microsoft.asc && \ 20 | dnf install -y https://packages.microsoft.com/config/rhel/9.0/packages-microsoft-prod.rpm && \ 21 | dnf install -y azure-cli 22 | 23 | # Install latest version of Terraform 24 | RUN LATEST_VERSION=$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | jq -r '.tag_name' | sed 's/v//') && \ 25 | curl -O "https://releases.hashicorp.com/terraform/${LATEST_VERSION}/terraform_${LATEST_VERSION}_linux_amd64.zip" && \ 26 | unzip terraform_${LATEST_VERSION}_linux_amd64.zip -d /usr/local/bin && \ 27 | rm terraform_${LATEST_VERSION}_linux_amd64.zip 28 | 29 | WORKDIR /workspace 30 | -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with hardcoded values 2 | resource "aws_vpc" "production" { 3 | cidr_block = "10.0.0.0/16" 4 | enable_dns_hostnames = true 5 | enable_dns_support = true 6 | 7 | tags = { 8 | Name = "production-vpc" 9 | Environment = "production" 10 | Project = "static-infrastructure" 11 | ManagedBy = "manual-deployment" 12 | Region = "us-east-1" 13 | AccountID = "123456789" 14 | } 15 | } 16 | 17 | resource "aws_subnet" "private" { 18 | vpc_id = aws_vpc.production.id 19 | cidr_block = "10.0.1.0/24" 20 | availability_zone = "us-east-1a" 21 | map_public_ip_on_launch = false 22 | 23 | tags = { 24 | Name = "production-private-subnet" 25 | Environment = "production" 26 | Project = "static-infrastructure" 27 | ManagedBy = "terraform" 28 | Region = "us-east-1" 29 | AZ = "us-east-1a" 30 | } 31 | } 32 | 33 | resource "aws_route_table" "static" { 34 | vpc_id = aws_vpc.production.id 35 | 36 | tags = { 37 | Name = "production-route-table" 38 | Environment = "production" 39 | Project = "static-infrastructure" 40 | ManagedBy = "terraform" 41 | Region = "us-east-1" 42 | } 43 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with repetitive elements 2 | resource "github_repository" "app" { 3 | name = "production-application" 4 | description = "Production application repository" 5 | visibility = "public" 6 | 7 | has_issues = true 8 | has_wiki = true 9 | has_discussions = true 10 | has_projects = true 11 | 12 | allow_merge_commit = true 13 | allow_rebase_merge = true 14 | allow_squash_merge = true 15 | 16 | delete_branch_on_merge = true 17 | auto_init = true 18 | 19 | topics = [ 20 | "production", 21 | "application", 22 | "terraform-demo", 23 | "infrastructure-team" 24 | ] 25 | } 26 | 27 | resource "github_repository" "docs" { 28 | name = "production-documentation" 29 | description = "Production documentation repository" 30 | visibility = "public" 31 | 32 | has_issues = true 33 | has_wiki = true 34 | has_discussions = true 35 | has_projects = true 36 | 37 | allow_merge_commit = true 38 | allow_rebase_merge = true 39 | allow_squash_merge = true 40 | 41 | delete_branch_on_merge = true 42 | auto_init = true 43 | 44 | topics = [ 45 | "production", 46 | "documentation", 47 | "terraform-demo", 48 | "infrastructure-team" 49 | ] 50 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Main VPC 2 | resource "aws_vpc" "main" { 3 | cidr_block = "10.0.0.0/16" 4 | enable_dns_hostnames = true 5 | enable_dns_support = true 6 | 7 | tags = { 8 | Name = "main-vpc" 9 | } 10 | } 11 | 12 | # Subnets created with count 13 | resource "aws_subnet" "subnet" { 14 | count = 3 15 | vpc_id = aws_vpc.main.id 16 | cidr_block = var.subnet_cidr_blocks[count.index] 17 | availability_zone = var.availability_zones[count.index] 18 | 19 | tags = { 20 | Name = "subnet-${count.index + 1}" 21 | Tier = count.index < 1 ? "public" : "private" 22 | } 23 | } 24 | 25 | # Security groups created with count 26 | resource "aws_security_group" "sg" { 27 | count = 3 28 | name = "${var.security_groups[count.index]}-sg" 29 | description = "Security group for ${var.security_groups[count.index]}" 30 | vpc_id = aws_vpc.main.id 31 | 32 | tags = { 33 | Name = "${var.security_groups[count.index]}-sg" 34 | } 35 | } 36 | 37 | # Security group rules created with count 38 | resource "aws_security_group_rule" "ingress" { 39 | count = 3 40 | type = "ingress" 41 | from_port = var.sg_ports[count.index] 42 | to_port = var.sg_ports[count.index] 43 | protocol = "tcp" 44 | cidr_blocks = ["0.0.0.0/0"] 45 | security_group_id = aws_security_group.sg[count.index].id 46 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/GITHUB/variables.tf: -------------------------------------------------------------------------------- 1 | variable "prefix" { 2 | description = "Prefix for resource names" 3 | type = string 4 | default = "tflab16" 5 | } 6 | 7 | variable "repository_visibility" { 8 | description = "Repository visibility setting" 9 | type = string 10 | default = "public" 11 | } 12 | 13 | variable "repository_description" { 14 | description = "Description for the repository" 15 | type = string 16 | default = "Repository created for Terraform Lab 16" 17 | } 18 | 19 | variable "repository_topics" { 20 | description = "Topics for the repository" 21 | type = list(string) 22 | default = ["terraform", "lab", "example"] 23 | } 24 | 25 | variable "auto_init" { 26 | description = "Initialize repository with README" 27 | type = bool 28 | default = true 29 | } 30 | 31 | variable "gitignore_template" { 32 | description = "Template for gitignore file" 33 | type = string 34 | default = "Terraform" 35 | } 36 | 37 | variable "random_suffix_length" { 38 | description = "Length of random suffix for unique resource names" 39 | type = number 40 | default = 6 41 | } 42 | 43 | variable "special_chars_allowed" { 44 | description = "Allow special characters in random string" 45 | type = bool 46 | default = false 47 | } 48 | 49 | variable "upper_chars_allowed" { 50 | description = "Allow uppercase characters in random string" 51 | type = bool 52 | default = false 53 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # GitHub Repository 2 | resource "github_repository" "example" { 3 | name = "${var.prefix}-repo-${random_string.suffix.result}" 4 | description = var.repository_description 5 | visibility = var.repository_visibility 6 | 7 | auto_init = var.auto_init 8 | gitignore_template = var.gitignore_template 9 | topics = var.repository_topics 10 | } 11 | 12 | # GitHub Branch 13 | resource "github_branch" "development" { 14 | repository = github_repository.example.name 15 | branch = "development" 16 | } 17 | 18 | # Branch Protection 19 | resource "github_branch_protection" "main" { 20 | repository_id = github_repository.example.node_id 21 | pattern = "main" 22 | 23 | required_pull_request_reviews { 24 | dismiss_stale_reviews = true 25 | required_approving_review_count = 1 26 | } 27 | } 28 | 29 | # Repository File 30 | resource "github_repository_file" "readme" { 31 | repository = github_repository.example.name 32 | branch = "main" 33 | file = "README.md" 34 | content = "# ${github_repository.example.name}\n\n${var.repository_description}\n" 35 | commit_message = "Update README" 36 | commit_author = "Terraform" 37 | commit_email = "terraform@example.com" 38 | overwrite_on_create = true 39 | } 40 | 41 | # Random string for resource name uniqueness 42 | resource "random_string" "suffix" { 43 | length = var.random_suffix_length 44 | special = var.special_chars_allowed 45 | upper = var.upper_chars_allowed 46 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Get ARN of user to use for S3 bucket policy 2 | data "aws_caller_identity" "current" {} 3 | 4 | # Random string for uniqueness 5 | resource "random_string" "suffix" { 6 | length = 8 7 | special = false 8 | upper = false 9 | } 10 | 11 | # VPC 12 | resource "aws_vpc" "main" { 13 | cidr_block = var.vpc_cidr 14 | enable_dns_support = true 15 | enable_dns_hostnames = true 16 | 17 | tags = { 18 | Name = "main-vpc" 19 | Environment = var.environment 20 | } 21 | } 22 | 23 | # Subnet 24 | resource "aws_subnet" "public" { 25 | vpc_id = aws_vpc.main.id 26 | cidr_block = var.subnet_cidr 27 | availability_zone = "${var.region}a" 28 | 29 | tags = { 30 | Name = "public-subnet" 31 | Environment = var.environment 32 | } 33 | } 34 | 35 | # Internet Gateway 36 | resource "aws_internet_gateway" "igw" { 37 | vpc_id = aws_vpc.main.id 38 | 39 | tags = { 40 | Name = "main-igw" 41 | Environment = var.environment 42 | } 43 | } 44 | 45 | # Route Table 46 | resource "aws_route_table" "public" { 47 | vpc_id = aws_vpc.main.id 48 | 49 | route { 50 | cidr_block = "0.0.0.0/0" 51 | gateway_id = aws_internet_gateway.igw.id 52 | } 53 | 54 | tags = { 55 | Name = "public-route-table" 56 | Environment = var.environment 57 | } 58 | } 59 | 60 | # S3 Bucket 61 | resource "aws_s3_bucket" "logs" { 62 | bucket = "logs-${random_string.suffix.result}" 63 | 64 | tags = { 65 | Name = "logs-bucket" 66 | Environment = var.environment 67 | } 68 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Creative Commons Attribution-NonCommercial 4.0 International 2 | 3 | This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License. 4 | 5 | You are free to: 6 | * Share — copy and redistribute the material in any medium or format 7 | * Adapt — remix, transform, and build upon the material 8 | 9 | Under the following terms: 10 | * Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 11 | * NonCommercial — You may not use the material for commercial purposes. 12 | 13 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 14 | 15 | ## Notices: 16 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 17 | 18 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 19 | 20 | Learn more about this license at: https://creativecommons.org/licenses/by-nc/4.0/ 21 | 22 | ## Simple Summary 23 | This means: 24 | 1. You can freely use and modify this work for personal, non-commercial purposes 25 | 2. You must credit the original creator 26 | 3. You cannot use this work for commercial purposes without explicit permission 27 | 4. You cannot prevent others from making use of this work under these same conditions 28 | -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # S3 Bucket 2 | resource "aws_s3_bucket" "example" { 3 | bucket = "${var.prefix}-example-${random_string.suffix.result}" 4 | 5 | tags = { 6 | Name = var.bucket_tag_name 7 | Environment = var.environment 8 | Lab = var.lab_name 9 | } 10 | } 11 | 12 | # IAM Role 13 | resource "aws_iam_role" "example" { 14 | name = "${var.prefix}-example-role" 15 | 16 | assume_role_policy = jsonencode({ 17 | Version = "2012-10-17" 18 | Statement = [ 19 | { 20 | Action = "sts:AssumeRole" 21 | Effect = var.effect_type 22 | Principal = { 23 | Service = var.assume_role_service 24 | } 25 | } 26 | ] 27 | }) 28 | 29 | tags = { 30 | Lab = var.lab_name 31 | Environment = var.environment 32 | } 33 | } 34 | 35 | # IAM Policy 36 | resource "aws_iam_policy" "example" { 37 | name = "${var.prefix}-example-policy" 38 | description = "${var.policy_description} for ${var.lab_name}" 39 | 40 | policy = jsonencode({ 41 | Version = "2012-10-17" 42 | Statement = [ 43 | { 44 | Action = var.policy_actions 45 | Effect = var.effect_type 46 | Resource = aws_s3_bucket.example.arn 47 | } 48 | ] 49 | }) 50 | } 51 | 52 | # Policy Attachment 53 | resource "aws_iam_role_policy_attachment" "example" { 54 | role = aws_iam_role.example.name 55 | policy_arn = aws_iam_policy.example.arn 56 | } 57 | 58 | # Random string for bucket name uniqueness 59 | resource "random_string" "suffix" { 60 | length = var.random_suffix_length 61 | special = var.special_chars_allowed 62 | upper = var.upper_chars_allowed 63 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AZURE/variables.tf: -------------------------------------------------------------------------------- 1 | variable "azure_location" { 2 | description = "Azure region to deploy resources" 3 | type = string 4 | default = "eastus" 5 | } 6 | 7 | variable "prefix" { 8 | description = "Prefix for resource names" 9 | type = string 10 | default = "tf" 11 | } 12 | 13 | variable "environment" { 14 | description = "Environment name for tagging" 15 | type = string 16 | default = "dev" 17 | } 18 | 19 | variable "lab_name" { 20 | description = "Lab identifier for tagging" 21 | type = string 22 | default = "lab16" 23 | } 24 | 25 | variable "random_suffix_length" { 26 | description = "Length of random suffix for unique resource names" 27 | type = number 28 | default = 5 29 | } 30 | 31 | variable "allowed_ip_ranges" { 32 | description = "List of allowed IP ranges for the storage account" 33 | type = list(string) 34 | default = ["0.0.0.0/0"] 35 | } 36 | 37 | variable "storage_account_tag_name" { 38 | description = "Name tag for the storage account" 39 | type = string 40 | default = "example-storage" 41 | } 42 | 43 | variable "role_definition_name" { 44 | description = "Name of the built-in role definition" 45 | type = string 46 | default = "Storage Blob Data Reader" 47 | } 48 | 49 | variable "policy_description" { 50 | description = "Description for the Azure policy" 51 | type = string 52 | default = "Example policy for lab exercises" 53 | } 54 | 55 | variable "special_chars_allowed" { 56 | description = "Allow special characters in random string" 57 | type = bool 58 | default = false 59 | } 60 | 61 | variable "upper_chars_allowed" { 62 | description = "Allow uppercase characters in random string" 63 | type = bool 64 | default = false 65 | } -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group in primary region 2 | resource "azurerm_resource_group" "primary" { 3 | provider = azurerm.primary 4 | name = "rg-${var.environment}-primary" 5 | location = var.primary_location 6 | 7 | tags = { 8 | Environment = var.environment 9 | Region = var.primary_location 10 | } 11 | } 12 | 13 | # Resource Group in secondary region 14 | resource "azurerm_resource_group" "secondary" { 15 | provider = azurerm.secondary 16 | name = "rg-${var.environment}-secondary" 17 | location = var.secondary_location 18 | 19 | tags = { 20 | Environment = var.environment 21 | Region = var.secondary_location 22 | } 23 | } 24 | 25 | # Storage Account in primary region 26 | resource "azurerm_storage_account" "primary" { 27 | provider = azurerm.primary 28 | name = "sa${var.environment}${formatdate("YYMMDD", timestamp())}" 29 | resource_group_name = azurerm_resource_group.primary.name 30 | location = azurerm_resource_group.primary.location 31 | account_tier = "Standard" 32 | account_replication_type = "LRS" 33 | 34 | tags = { 35 | Environment = var.environment 36 | Region = var.primary_location 37 | } 38 | } 39 | 40 | # Storage Account in secondary region 41 | resource "azurerm_storage_account" "secondary" { 42 | provider = azurerm.secondary 43 | name = "sa${var.environment}sec${formatdate("YYMMDD", timestamp())}" 44 | resource_group_name = azurerm_resource_group.secondary.name 45 | location = azurerm_resource_group.secondary.location 46 | account_tier = "Standard" 47 | account_replication_type = "LRS" 48 | 49 | tags = { 50 | Environment = var.environment 51 | Region = var.secondary_location 52 | } 53 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AWS/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region to deploy resources" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "prefix" { 8 | description = "Prefix for resource names" 9 | type = string 10 | default = "tf-lab16" 11 | } 12 | 13 | variable "environment" { 14 | description = "Environment name for tagging" 15 | type = string 16 | default = "dev" 17 | } 18 | 19 | variable "lab_name" { 20 | description = "Lab identifier for tagging" 21 | type = string 22 | default = "lab16" 23 | } 24 | 25 | variable "random_suffix_length" { 26 | description = "Length of random suffix for unique resource names" 27 | type = number 28 | default = 8 29 | } 30 | 31 | variable "assume_role_service" { 32 | description = "Service that can assume the IAM role" 33 | type = string 34 | default = "s3.amazonaws.com" 35 | } 36 | 37 | variable "policy_actions" { 38 | description = "List of actions to allow in the IAM policy" 39 | type = list(string) 40 | default = ["s3:ListBucket"] 41 | } 42 | 43 | variable "bucket_tag_name" { 44 | description = "Name tag for the bucket" 45 | type = string 46 | default = "example-bucket" 47 | } 48 | 49 | variable "effect_type" { 50 | description = "Effect type for IAM policies" 51 | type = string 52 | default = "Allow" 53 | } 54 | 55 | variable "policy_description" { 56 | description = "Description for the IAM policy" 57 | type = string 58 | default = "Example policy for lab exercises" 59 | } 60 | 61 | variable "special_chars_allowed" { 62 | description = "Allow special characters in random string" 63 | type = bool 64 | default = false 65 | } 66 | 67 | variable "upper_chars_allowed" { 68 | description = "Allow uppercase characters in random string" 69 | type = bool 70 | default = false 71 | } -------------------------------------------------------------------------------- /labs/lab_09_deploying_mulitple_resources_with_for_each/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group 2 | resource "azurerm_resource_group" "main" { 3 | name = "primary-resources" 4 | location = "eastus" 5 | 6 | tags = { 7 | Environment = "Development" 8 | } 9 | } 10 | 11 | # Virtual Networks created with count 12 | resource "azurerm_virtual_network" "vnet_count" { 13 | count = 3 14 | name = "vnet-count-${count.index + 1}" 15 | address_space = ["10.${count.index}.0.0/16"] 16 | location = azurerm_resource_group.main.location 17 | resource_group_name = azurerm_resource_group.main.name 18 | 19 | tags = { 20 | Environment = "Development" 21 | Network = "Network-${count.index + 1}" 22 | } 23 | } 24 | 25 | # Subnets created with count 26 | resource "azurerm_subnet" "subnet_count" { 27 | count = 3 28 | name = "subnet-count-${count.index + 1}" 29 | resource_group_name = azurerm_resource_group.main.name 30 | virtual_network_name = azurerm_virtual_network.vnet_count[0].name 31 | address_prefixes = ["10.0.${count.index + 1}.0/24"] 32 | } 33 | 34 | # Network Security Groups created with count 35 | resource "azurerm_network_security_group" "nsg_count" { 36 | count = 3 37 | name = "nsg-count-${count.index + 1}" 38 | location = azurerm_resource_group.main.location 39 | resource_group_name = azurerm_resource_group.main.name 40 | 41 | security_rule { 42 | name = "rule-${count.index + 1}" 43 | priority = 100 + count.index 44 | direction = "Inbound" 45 | access = "Allow" 46 | protocol = "Tcp" 47 | source_port_range = "*" 48 | destination_port_range = tostring(80 + count.index * 1000) 49 | source_address_prefix = "*" 50 | destination_address_prefix = "*" 51 | } 52 | 53 | tags = { 54 | Environment = "Development" 55 | Type = "NSG-${count.index + 1}" 56 | } 57 | } -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group 2 | resource "azurerm_resource_group" "example" { 3 | name = "depends-on-${var.environment}-rg" 4 | location = var.location 5 | 6 | tags = { 7 | Environment = var.environment 8 | } 9 | } 10 | 11 | # Virtual Network 12 | resource "azurerm_virtual_network" "example" { 13 | name = "${var.environment}-vnet" 14 | address_space = var.vnet_address_space 15 | location = azurerm_resource_group.example.location 16 | resource_group_name = azurerm_resource_group.example.name 17 | 18 | tags = { 19 | Environment = var.environment 20 | } 21 | } 22 | 23 | # Subnet 24 | resource "azurerm_subnet" "example" { 25 | name = "${var.environment}-subnet" 26 | resource_group_name = azurerm_resource_group.example.name 27 | virtual_network_name = azurerm_virtual_network.example.name 28 | address_prefixes = var.subnet_address_prefix 29 | } 30 | 31 | # Network Security Group 32 | resource "azurerm_network_security_group" "example" { 33 | name = "${var.environment}-nsg" 34 | location = azurerm_resource_group.example.location 35 | resource_group_name = azurerm_resource_group.example.name 36 | 37 | security_rule { 38 | name = "AllowHTTP" 39 | priority = 100 40 | direction = "Inbound" 41 | access = "Allow" 42 | protocol = "Tcp" 43 | source_port_range = "*" 44 | destination_port_range = "80" 45 | source_address_prefix = "*" 46 | destination_address_prefix = "*" 47 | } 48 | 49 | tags = { 50 | Environment = var.environment 51 | } 52 | } 53 | 54 | # Storage Account 55 | resource "azurerm_storage_account" "example" { 56 | name = "sa${var.environment}${formatdate("YYMMDD", timestamp())}" 57 | resource_group_name = azurerm_resource_group.example.name 58 | location = azurerm_resource_group.example.location 59 | account_tier = "Standard" 60 | account_replication_type = "LRS" 61 | 62 | tags = { 63 | Environment = var.environment 64 | } 65 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/GITHUB/main.tf: -------------------------------------------------------------------------------- 1 | # Individual Repository Resources 2 | resource "github_repository" "repo1" { 3 | name = "example-repo-1" 4 | description = "Example repository 1" 5 | visibility = "public" 6 | auto_init = true 7 | 8 | topics = ["example", "terraform", "repo1"] 9 | } 10 | 11 | resource "github_repository" "repo2" { 12 | name = "example-repo-2" 13 | description = "Example repository 2" 14 | visibility = "public" 15 | auto_init = true 16 | 17 | topics = ["example", "terraform", "repo2"] 18 | } 19 | 20 | resource "github_repository" "repo3" { 21 | name = "example-repo-3" 22 | description = "Example repository 3" 23 | visibility = "public" 24 | auto_init = true 25 | 26 | topics = ["example", "terraform", "repo3"] 27 | } 28 | 29 | # Individual Branch Protection Resources 30 | resource "github_branch_protection" "protection1" { 31 | repository_id = github_repository.repo1.node_id 32 | pattern = "main" 33 | 34 | allows_deletions = false 35 | allows_force_pushes = false 36 | require_conversation_resolution = true 37 | } 38 | 39 | resource "github_branch_protection" "protection2" { 40 | repository_id = github_repository.repo2.node_id 41 | pattern = "main" 42 | 43 | allows_deletions = false 44 | allows_force_pushes = false 45 | require_conversation_resolution = true 46 | } 47 | 48 | resource "github_branch_protection" "protection3" { 49 | repository_id = github_repository.repo3.node_id 50 | pattern = "main" 51 | 52 | allows_deletions = false 53 | allows_force_pushes = false 54 | require_conversation_resolution = true 55 | } 56 | 57 | # Individual Issue Label Resources 58 | resource "github_issue_label" "bug1" { 59 | repository = github_repository.repo1.name 60 | name = "bug" 61 | color = "FF0000" 62 | description = "Bug issues" 63 | } 64 | 65 | resource "github_issue_label" "feature1" { 66 | repository = github_repository.repo1.name 67 | name = "feature" 68 | color = "00FF00" 69 | description = "Feature requests" 70 | } 71 | 72 | resource "github_issue_label" "bug2" { 73 | repository = github_repository.repo2.name 74 | name = "bug" 75 | color = "FF0000" 76 | description = "Bug issues" 77 | } 78 | 79 | resource "github_issue_label" "feature2" { 80 | repository = github_repository.repo2.name 81 | name = "feature" 82 | color = "00FF00" 83 | description = "Feature requests" 84 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group 2 | resource "azurerm_resource_group" "example" { 3 | name = "${var.prefix}-resources" 4 | location = var.azure_location 5 | 6 | tags = { 7 | Environment = var.environment 8 | Lab = var.lab_name 9 | } 10 | } 11 | 12 | # Storage Account 13 | resource "azurerm_storage_account" "example" { 14 | name = "${var.prefix}storage${random_string.suffix.result}" 15 | resource_group_name = azurerm_resource_group.example.name 16 | location = azurerm_resource_group.example.location 17 | account_tier = "Standard" 18 | account_replication_type = "LRS" 19 | 20 | tags = { 21 | Name = var.storage_account_tag_name 22 | Environment = var.environment 23 | Lab = var.lab_name 24 | } 25 | } 26 | 27 | # Storage Container 28 | resource "azurerm_storage_container" "example" { 29 | name = "${var.prefix}-container" 30 | storage_account_id = azurerm_storage_account.example.id 31 | container_access_type = "private" 32 | } 33 | 34 | # User-Assigned Identity 35 | resource "azurerm_user_assigned_identity" "example" { 36 | resource_group_name = azurerm_resource_group.example.name 37 | location = azurerm_resource_group.example.location 38 | name = "${var.prefix}-identity" 39 | 40 | tags = { 41 | Lab = var.lab_name 42 | Environment = var.environment 43 | } 44 | } 45 | 46 | # Role Assignment 47 | resource "azurerm_role_assignment" "example" { 48 | scope = azurerm_storage_account.example.id 49 | role_definition_name = var.role_definition_name 50 | principal_id = azurerm_user_assigned_identity.example.principal_id 51 | } 52 | 53 | # Policy Definition 54 | resource "azurerm_policy_definition" "example" { 55 | name = "${var.prefix}-policy-definition" 56 | policy_type = "Custom" 57 | mode = "Indexed" 58 | display_name = "${var.prefix}-storage-policy" 59 | description = "${var.policy_description} for ${var.lab_name}" 60 | 61 | policy_rule = jsonencode({ 62 | if = { 63 | field = "type" 64 | equals = "Microsoft.Storage/storageAccounts" 65 | } 66 | then = { 67 | effect = "audit" 68 | } 69 | }) 70 | 71 | metadata = jsonencode({ 72 | category = "Storage" 73 | Lab = var.lab_name 74 | }) 75 | } 76 | 77 | # Random string for storage account name uniqueness 78 | resource "random_string" "suffix" { 79 | length = var.random_suffix_length 80 | special = var.special_chars_allowed 81 | upper = var.upper_chars_allowed 82 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Basic VPC Configuration 2 | resource "aws_vpc" "main" { 3 | cidr_block = "10.0.0.0/16" 4 | enable_dns_hostnames = true 5 | enable_dns_support = true 6 | 7 | tags = { 8 | Name = "main-vpc" 9 | } 10 | } 11 | 12 | # Individual Subnet Resources 13 | resource "aws_subnet" "subnet_1" { 14 | vpc_id = aws_vpc.main.id 15 | cidr_block = "10.0.1.0/24" 16 | availability_zone = "us-east-1a" 17 | 18 | tags = { 19 | Name = "subnet-1" 20 | } 21 | } 22 | 23 | resource "aws_subnet" "subnet_2" { 24 | vpc_id = aws_vpc.main.id 25 | cidr_block = "10.0.2.0/24" 26 | availability_zone = "us-east-1b" 27 | 28 | tags = { 29 | Name = "subnet-2" 30 | } 31 | } 32 | 33 | resource "aws_subnet" "subnet_3" { 34 | vpc_id = aws_vpc.main.id 35 | cidr_block = "10.0.3.0/24" 36 | availability_zone = "us-east-1c" 37 | 38 | tags = { 39 | Name = "subnet-3" 40 | } 41 | } 42 | 43 | # Security Groups 44 | resource "aws_security_group" "web" { 45 | name = "web-sg" 46 | description = "Allow web traffic" 47 | vpc_id = aws_vpc.main.id 48 | 49 | ingress { 50 | from_port = 80 51 | to_port = 80 52 | protocol = "tcp" 53 | cidr_blocks = ["0.0.0.0/0"] 54 | } 55 | 56 | egress { 57 | from_port = 0 58 | to_port = 0 59 | protocol = "-1" 60 | cidr_blocks = ["0.0.0.0/0"] 61 | } 62 | 63 | tags = { 64 | Name = "web-sg" 65 | } 66 | } 67 | 68 | resource "aws_security_group" "app" { 69 | name = "app-sg" 70 | description = "Allow application traffic" 71 | vpc_id = aws_vpc.main.id 72 | 73 | ingress { 74 | from_port = 8080 75 | to_port = 8080 76 | protocol = "tcp" 77 | cidr_blocks = ["10.0.0.0/16"] 78 | } 79 | 80 | egress { 81 | from_port = 0 82 | to_port = 0 83 | protocol = "-1" 84 | cidr_blocks = ["0.0.0.0/0"] 85 | } 86 | 87 | tags = { 88 | Name = "app-sg" 89 | } 90 | } 91 | 92 | resource "aws_security_group" "db" { 93 | name = "db-sg" 94 | description = "Allow database traffic" 95 | vpc_id = aws_vpc.main.id 96 | 97 | ingress { 98 | from_port = 3306 99 | to_port = 3306 100 | protocol = "tcp" 101 | cidr_blocks = ["10.0.0.0/16"] 102 | } 103 | 104 | egress { 105 | from_port = 0 106 | to_port = 0 107 | protocol = "-1" 108 | cidr_blocks = ["0.0.0.0/0"] 109 | } 110 | 111 | tags = { 112 | Name = "db-sg" 113 | } 114 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with repetitive elements 2 | resource "azurerm_resource_group" "main" { 3 | name = "production-resources" 4 | location = "eastus" 5 | 6 | tags = { 7 | Name = "production-resources" 8 | Environment = "production" 9 | Project = "terraform-demo" 10 | Owner = "infrastructure-team" 11 | CostCenter = "cc-1234" 12 | Region = "eastus" 13 | } 14 | } 15 | 16 | resource "azurerm_virtual_network" "main" { 17 | name = "production-vnet" 18 | resource_group_name = azurerm_resource_group.main.name 19 | location = azurerm_resource_group.main.location 20 | address_space = ["10.0.0.0/16"] 21 | 22 | tags = { 23 | Name = "production-vnet" 24 | Environment = "production" 25 | Project = "terraform-demo" 26 | Owner = "infrastructure-team" 27 | CostCenter = "cc-1234" 28 | Region = "eastus" 29 | } 30 | } 31 | 32 | resource "azurerm_subnet" "web" { 33 | name = "production-web-subnet" 34 | resource_group_name = azurerm_resource_group.main.name 35 | virtual_network_name = azurerm_virtual_network.main.name 36 | address_prefixes = ["10.0.1.0/24"] 37 | } 38 | 39 | resource "azurerm_subnet" "app" { 40 | name = "production-app-subnet" 41 | resource_group_name = azurerm_resource_group.main.name 42 | virtual_network_name = azurerm_virtual_network.main.name 43 | address_prefixes = ["10.0.2.0/24"] 44 | } 45 | 46 | resource "azurerm_subnet" "db" { 47 | name = "production-db-subnet" 48 | resource_group_name = azurerm_resource_group.main.name 49 | virtual_network_name = azurerm_virtual_network.main.name 50 | address_prefixes = ["10.0.3.0/24"] 51 | } 52 | 53 | resource "azurerm_network_security_group" "web" { 54 | name = "production-web-nsg" 55 | location = azurerm_resource_group.main.location 56 | resource_group_name = azurerm_resource_group.main.name 57 | 58 | security_rule { 59 | name = "allow-http" 60 | priority = 100 61 | direction = "Inbound" 62 | access = "Allow" 63 | protocol = "Tcp" 64 | source_port_range = "*" 65 | destination_port_range = "80" 66 | source_address_prefix = "*" 67 | destination_address_prefix = "*" 68 | } 69 | 70 | security_rule { 71 | name = "allow-https" 72 | priority = 110 73 | direction = "Inbound" 74 | access = "Allow" 75 | protocol = "Tcp" 76 | source_port_range = "*" 77 | destination_port_range = "443" 78 | source_address_prefix = "*" 79 | destination_address_prefix = "*" 80 | } 81 | 82 | tags = { 83 | Name = "production-web-nsg" 84 | Environment = "production" 85 | Project = "terraform-demo" 86 | Owner = "infrastructure-team" 87 | CostCenter = "cc-1234" 88 | Region = "eastus" 89 | } 90 | } -------------------------------------------------------------------------------- /labs/lab_07_simplify_code_with_local_values/AWS/main.tf: -------------------------------------------------------------------------------- 1 | # Static configuration with repetitive elements 2 | resource "aws_vpc" "main" { 3 | cidr_block = "10.0.0.0/16" 4 | enable_dns_hostnames = true 5 | enable_dns_support = true 6 | 7 | tags = { 8 | Name = "production-vpc-us-east-1" 9 | Environment = "production" 10 | Project = "terraform-demo" 11 | Owner = "infrastructure-team" 12 | CostCenter = "cc-1234" 13 | Region = "us-east-1" 14 | ManagedBy = "terraform" 15 | } 16 | } 17 | 18 | resource "aws_subnet" "public_a" { 19 | vpc_id = aws_vpc.main.id 20 | cidr_block = "10.0.1.0/24" 21 | availability_zone = "us-east-1a" 22 | map_public_ip_on_launch = true 23 | 24 | tags = { 25 | Name = "production-public-subnet-us-east-1a" 26 | Environment = "production" 27 | Project = "terraform-demo" 28 | Owner = "infrastructure-team" 29 | CostCenter = "cc-1234" 30 | Region = "us-east-1" 31 | ManagedBy = "terraform" 32 | Tier = "public" 33 | } 34 | } 35 | 36 | resource "aws_subnet" "public_b" { 37 | vpc_id = aws_vpc.main.id 38 | cidr_block = "10.0.2.0/24" 39 | availability_zone = "us-east-1b" 40 | map_public_ip_on_launch = true 41 | 42 | tags = { 43 | Name = "production-public-subnet-us-east-1b" 44 | Environment = "production" 45 | Project = "terraform-demo" 46 | Owner = "infrastructure-team" 47 | CostCenter = "cc-1234" 48 | Region = "us-east-1" 49 | ManagedBy = "terraform" 50 | Tier = "public" 51 | } 52 | } 53 | 54 | resource "aws_subnet" "private_a" { 55 | vpc_id = aws_vpc.main.id 56 | cidr_block = "10.0.3.0/24" 57 | availability_zone = "us-east-1a" 58 | map_public_ip_on_launch = false 59 | 60 | tags = { 61 | Name = "production-private-subnet-us-east-1a" 62 | Environment = "production" 63 | Project = "terraform-demo" 64 | Owner = "infrastructure-team" 65 | CostCenter = "cc-1234" 66 | Region = "us-east-1" 67 | ManagedBy = "terraform" 68 | Tier = "private" 69 | } 70 | } 71 | 72 | resource "aws_subnet" "private_b" { 73 | vpc_id = aws_vpc.main.id 74 | cidr_block = "10.0.4.0/24" 75 | availability_zone = "us-east-1b" 76 | map_public_ip_on_launch = false 77 | 78 | tags = { 79 | Name = "production-private-subnet-us-east-1b" 80 | Environment = "production" 81 | Project = "terraform-demo" 82 | Owner = "infrastructure-team" 83 | CostCenter = "cc-1234" 84 | Region = "us-east-1" 85 | ManagedBy = "terraform" 86 | Tier = "private" 87 | } 88 | } 89 | 90 | resource "aws_security_group" "web" { 91 | name = "production-web-sg" 92 | description = "Allow web traffic" 93 | vpc_id = aws_vpc.main.id 94 | 95 | ingress { 96 | from_port = 80 97 | to_port = 80 98 | protocol = "tcp" 99 | cidr_blocks = ["0.0.0.0/0"] 100 | } 101 | 102 | ingress { 103 | from_port = 443 104 | to_port = 443 105 | protocol = "tcp" 106 | cidr_blocks = ["0.0.0.0/0"] 107 | } 108 | 109 | egress { 110 | from_port = 0 111 | to_port = 0 112 | protocol = "-1" 113 | cidr_blocks = ["0.0.0.0/0"] 114 | } 115 | 116 | tags = { 117 | Name = "production-web-sg" 118 | Environment = "production" 119 | Project = "terraform-demo" 120 | Owner = "infrastructure-team" 121 | CostCenter = "cc-1234" 122 | Region = "us-east-1" 123 | ManagedBy = "terraform" 124 | } 125 | } -------------------------------------------------------------------------------- /labs/lab_08_create_mulitple_resources_with_count/AZURE/main.tf: -------------------------------------------------------------------------------- 1 | # Resource Group 2 | resource "azurerm_resource_group" "main" { 3 | name = "main-resources" 4 | location = "eastus" 5 | 6 | tags = { 7 | Environment = "Development" 8 | } 9 | } 10 | 11 | # Individual Virtual Networks 12 | resource "azurerm_virtual_network" "vnet1" { 13 | name = "vnet-1" 14 | address_space = ["10.1.0.0/16"] 15 | location = azurerm_resource_group.main.location 16 | resource_group_name = azurerm_resource_group.main.name 17 | 18 | tags = { 19 | Environment = "Development" 20 | Network = "VNet1" 21 | } 22 | } 23 | 24 | resource "azurerm_virtual_network" "vnet2" { 25 | name = "vnet-2" 26 | address_space = ["10.2.0.0/16"] 27 | location = azurerm_resource_group.main.location 28 | resource_group_name = azurerm_resource_group.main.name 29 | 30 | tags = { 31 | Environment = "Development" 32 | Network = "VNet2" 33 | } 34 | } 35 | 36 | resource "azurerm_virtual_network" "vnet3" { 37 | name = "vnet-3" 38 | address_space = ["10.3.0.0/16"] 39 | location = azurerm_resource_group.main.location 40 | resource_group_name = azurerm_resource_group.main.name 41 | 42 | tags = { 43 | Environment = "Development" 44 | Network = "VNet3" 45 | } 46 | } 47 | 48 | # Individual Subnets 49 | resource "azurerm_subnet" "subnet1" { 50 | name = "subnet-1" 51 | resource_group_name = azurerm_resource_group.main.name 52 | virtual_network_name = azurerm_virtual_network.vnet1.name 53 | address_prefixes = ["10.1.1.0/24"] 54 | } 55 | 56 | resource "azurerm_subnet" "subnet2" { 57 | name = "subnet-2" 58 | resource_group_name = azurerm_resource_group.main.name 59 | virtual_network_name = azurerm_virtual_network.vnet1.name 60 | address_prefixes = ["10.1.2.0/24"] 61 | } 62 | 63 | # Individual Network Security Groups 64 | resource "azurerm_network_security_group" "web" { 65 | name = "web-nsg" 66 | location = azurerm_resource_group.main.location 67 | resource_group_name = azurerm_resource_group.main.name 68 | 69 | security_rule { 70 | name = "allow-http" 71 | priority = 100 72 | direction = "Inbound" 73 | access = "Allow" 74 | protocol = "Tcp" 75 | source_port_range = "*" 76 | destination_port_range = "80" 77 | source_address_prefix = "*" 78 | destination_address_prefix = "*" 79 | } 80 | 81 | tags = { 82 | Environment = "Development" 83 | Role = "Web" 84 | } 85 | } 86 | 87 | resource "azurerm_network_security_group" "app" { 88 | name = "app-nsg" 89 | location = azurerm_resource_group.main.location 90 | resource_group_name = azurerm_resource_group.main.name 91 | 92 | security_rule { 93 | name = "allow-app" 94 | priority = 100 95 | direction = "Inbound" 96 | access = "Allow" 97 | protocol = "Tcp" 98 | source_port_range = "*" 99 | destination_port_range = "8080" 100 | source_address_prefix = "10.1.0.0/16" 101 | destination_address_prefix = "*" 102 | } 103 | 104 | tags = { 105 | Environment = "Development" 106 | Role = "App" 107 | } 108 | } 109 | 110 | resource "azurerm_network_security_group" "db" { 111 | name = "db-nsg" 112 | location = azurerm_resource_group.main.location 113 | resource_group_name = azurerm_resource_group.main.name 114 | 115 | security_rule { 116 | name = "allow-sql" 117 | priority = 100 118 | direction = "Inbound" 119 | access = "Allow" 120 | protocol = "Tcp" 121 | source_port_range = "*" 122 | destination_port_range = "1433" 123 | source_address_prefix = "10.1.0.0/16" 124 | destination_address_prefix = "*" 125 | } 126 | 127 | tags = { 128 | Environment = "Development" 129 | Role = "DB" 130 | } 131 | } -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/GITHUB/github.md: -------------------------------------------------------------------------------- 1 | # LAB-16-GITHUB: Replacing and Removing Resources in Terraform 2 | 3 | ## Overview 4 | In this lab, you will learn how to replace and remove resources in Terraform when working with GitHub. You'll practice using the `-replace` flag and removing resources from configuration using GitHub resources. 5 | 6 | [![Lab 16](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | ## Prerequisites 9 | - Terraform installed (v1.0.0+) 10 | - GitHub account 11 | - GitHub personal access token with appropriate permissions 12 | 13 | ## Estimated Time 14 | 30 minutes 15 | 16 | ## Initial Configuration Files 17 | 18 | Create the following files in your working directory: 19 | 20 | - `variables.tf` 21 | - `main.tf` 22 | - `providers.tf` 23 | - `outputs.tf` 24 | 25 | ## Lab Steps 26 | 27 | ### 1. Configure GitHub Credentials 28 | 29 | ```bash 30 | export GITHUB_TOKEN="" 31 | ``` 32 | 33 | ### 2. Initialize and Apply 34 | ```bash 35 | terraform init 36 | terraform apply -auto-approve 37 | ``` 38 | 39 | ### 3. Replace a Resource Using the `-replace` Flag 40 | 41 | Let's replace the README file without changing its configuration: 42 | 43 | ```bash 44 | terraform apply -replace="github_repository_file.readme" -auto-approve 45 | ``` 46 | 47 | Observe in the output how Terraform: 48 | - Re-creates the file with the same content 49 | - Note that with GitHub files, this results in a new commit 50 | 51 | ### 4. Replace a Resource by Modifying Configuration 52 | 53 | Let's update the variables to change the repository's configuration. Create a file called `modified.tfvars`: 54 | 55 | ```hcl 56 | prefix = "tflab16mod" 57 | repository_description = "Modified repository for Terraform lab" 58 | repository_topics = ["terraform", "lab", "modified", "example"] 59 | ``` 60 | 61 | Apply with the new variables: 62 | 63 | ```bash 64 | terraform apply -var-file="modified.tfvars" -auto-approve 65 | ``` 66 | 67 | Observe how Terraform: 68 | - Creates a new repository (since the name changes) 69 | - Updates existing properties like description and topics 70 | 71 | ### 5. Remove a Resource by Deleting it from Configuration 72 | 73 | Remove or comment out the branch protection resource from `main.tf`: 74 | 75 | ```hcl 76 | # Branch Protection 77 | # resource "github_branch_protection" "main" { 78 | # repository_id = github_repository.example.node_id 79 | # pattern = "main" 80 | # 81 | # required_pull_request_reviews { 82 | # dismiss_stale_reviews = true 83 | # required_approving_review_count = 1 84 | # } 85 | # } 86 | ``` 87 | 88 | > Hint: You can quickly toggle single line comments by highlighting the lines and using the `Command` + `/` on Mac or `CTL` + `/` on Windows. 89 | 90 | Apply the changes: 91 | 92 | ```bash 93 | terraform apply -auto-approve 94 | ``` 95 | 96 | Observe that Terraform will remove the branch protection rule because it's no longer part of our configuration (because we commented it out). 97 | 98 | ### 6. Remove a Resource Using `terraform destroy -target` 99 | 100 | Now, let's remove the **development** branch using targeted destroy without changing the configuration: 101 | 102 | ```bash 103 | terraform destroy -target=github_branch.development -auto-approve 104 | ``` 105 | 106 | Notice that Terraform will destroy the GitHub Branch since we targeted that specific resource on a `terraform destroy` command. Type in `yes` to confirm and destroy the resource. 107 | 108 | Verify it's gone: 109 | 110 | ```bash 111 | terraform state list 112 | ``` 113 | 114 | You should NOT see a `github_branch.development` in the list of managed resources. 115 | 116 | 117 | Run a normal apply to recreate it - this is because we did NOT remove it from our desired configuration (`main.tf`) and Terraform compared the real-world resources to our desired configuration and, as a result, created the policy definition again. 118 | 119 | ```bash 120 | terraform apply -auto-approve 121 | ``` 122 | 123 | ### 7. Clean Up 124 | 125 | When finished, clean up all resources: 126 | 127 | ```bash 128 | terraform destroy -auto-approve 129 | ``` 130 | 131 | ## Key Concepts 132 | 133 | ### Resource Replacement Methods 134 | - **Using `-replace` flag**: Forces resource recreation without configuration changes 135 | - **Changing force-new attributes**: Some attribute changes automatically trigger replacement 136 | 137 | ### Resource Removal Methods 138 | - **Removing from configuration**: Delete the resource block from your .tf files 139 | - **Using `terraform destroy -target`**: Temporarily removes a resource; it will be recreated on next apply 140 | 141 | ## Additional Challenge 142 | 143 | 1. Add a GitHub team resource and provide it access to the repository, then practice replacing and removing it 144 | 2. Create a terraform.tfvars file that changes multiple variables at once, then observe which resources get replaced 145 | 3. Try adding webhook configurations to the repository and practice replacing them with different settings -------------------------------------------------------------------------------- /labs/lab_16_replace_a_single_resource/AZURE/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-16-Azure: Replacing and Removing Resources in Terraform 2 | 3 | ## Overview 4 | In this lab, you will learn how to replace and remove resources in Terraform. You'll practice using the `-replace` flag and removing resources from configuration using free Azure resources. 5 | 6 | [![Lab 16](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | ## Prerequisites 9 | - Terraform installed 10 | - Azure free tier account 11 | - Basic understanding of Terraform and Azure concepts 12 | 13 | Note: Azure credentials are required for this lab. 14 | 15 | ## How to Use This Hands-On Lab 16 | 17 | 1. **Create a Codespace** from this repo (click the button below). 18 | 2. Once the Codespace is running, open the integrated terminal. 19 | 3. Follow the instructions in each **lab** to complete the exercises. 20 | 21 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/[your-username]/terraform-codespaces) 22 | 23 | ## Estimated Time 24 | 30 minutes 25 | 26 | ## Initial Configuration Files 27 | 28 | Check out the following files in your working directory that contain Terrform configurations: 29 | 30 | - `variables.tf` 31 | - `main.tf` 32 | - `providers.tf` 33 | - `outputs.tf` 34 | 35 | > Note: Since we're focused on replacing and removing resources, this lab won't require writing a lot of Terraform. 36 | 37 | ## Lab Steps 38 | 39 | ### 1. Initialize and Apply 40 | ```bash 41 | terraform init 42 | terraform plan 43 | terraform apply -auto-approve 44 | ``` 45 | 46 | > Yes, the Azure provider is slooow. Be patient :) 47 | 48 | ### 2. Replace a Resource Using the `-replace` Flag 49 | 50 | Let's replace the user-assigned identity without changing its configuration: 51 | 52 | ```bash 53 | terraform apply -replace="azurerm_user_assigned_identity.example" -auto-approve 54 | ``` 55 | 56 | Observe in the output how Terraform: 57 | - Destroys the existing identity 58 | - Creates a new identity with the same configuration 59 | 60 | ### 3. Replace a Resource by Modifying Configuration 61 | 62 | Let's update the variables to change the storage account's configuration. Create a file called `modified.tfvars`: 63 | 64 | ```hcl 65 | prefix = "tfmod" 66 | storage_account_tag_name = "modified-storage" 67 | environment = "test" 68 | ``` 69 | 70 | Apply the configuration using the new variable values: 71 | 72 | ```bash 73 | terraform apply -var-file="modified.tfvars" -auto-approve 74 | ``` 75 | 76 | Observe how Terraform plans to replace the storage account due to the name change. 77 | 78 | ### 4. Remove a Resource by Deleting it from Configuration 79 | 80 | Remove or comment out the role assignment resource from `main.tf`: 81 | 82 | ```hcl 83 | # Role Assignment 84 | # resource "azurerm_role_assignment" "example" { 85 | # scope = azurerm_storage_account.example.id 86 | # role_definition_name = var.role_definition_name 87 | # principal_id = azurerm_user_assigned_identity.example.principal_id 88 | # } 89 | ``` 90 | 91 | > Hint: You can quickly toggle single line comments by highlighting the lines and using the `Command` + `/` on Mac or `CTL` + `/` on Windows. 92 | 93 | Apply the changes: 94 | 95 | ```bash 96 | terraform apply -auto-approve 97 | ``` 98 | 99 | Observe that Terraform plans to destroy the role assignment since we "removed" it from our `main.tf` file and it is no longer part of our desired configuration. 100 | 101 | ### 5. Remove a Resource Using `terraform destroy -target` 102 | 103 | Now, let's remove the policy definition using targeted destroy without changing the configuration: 104 | 105 | ```bash 106 | terraform destroy -target=azurerm_policy_definition.example 107 | ``` 108 | 109 | Notice that Terraform will destroy the policy definition since we targeted that specific resource on a `terraform destroy` command. Type in `yes` to confirm and destroy the resource. 110 | 111 | Verify it's gone: 112 | 113 | ```bash 114 | terraform state list 115 | ``` 116 | 117 | Run a normal apply to recreate it - this is because we did NOT remove it from our desired configuration (`main.tf`) and Terraform compared the real-world resources to our desired configuration and, as a result, created the policy definition again. 118 | 119 | ```bash 120 | terraform apply -auto-approve 121 | ``` 122 | 123 | ### 6. Clean Up 124 | 125 | When finished, clean up all resources and remove them from your account: 126 | 127 | ```bash 128 | terraform destroy -auto-approve 129 | ``` 130 | 131 | ## Key Concepts 132 | 133 | ### Resource Replacement Methods 134 | - **Using `-replace` flag**: Forces resource recreation without configuration changes 135 | - **Changing force-new attributes**: Some attribute changes automatically trigger replacement 136 | 137 | ### Resource Removal Methods 138 | - **Removing from configuration**: Delete the resource block from your .tf files 139 | - **Using `terraform destroy -target`**: Temporarily removes a resource; it will be recreated on next apply 140 | 141 | ## Additional Challenge 142 | 143 | 1. Create a terraform.tfvars file that changes multiple variables at once, then observe which resources get replaced 144 | 2. Try using `-replace` with a resource that has dependencies and observe how Terraform handles the dependencies 145 | -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/AWS/aws.md: -------------------------------------------------------------------------------- 1 | # LAB-13-AWS: Using Basic Terraform Functions 2 | 3 | ## Overview 4 | In this lab, you will learn how to use a few essential Terraform built-in functions: `min`, `max`, `join`, and `toset`. These functions help you manipulate values and create more flexible infrastructure configurations. The lab uses AWS free-tier resources to ensure no costs are incurred. 5 | 6 | [![Lab 13](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - AWS free tier account 13 | - Basic understanding of Terraform and AWS concepts 14 | 15 | Note: AWS credentials are required for this lab. 16 | 17 | ## How to Use This Hands-On Lab 18 | 19 | 1. **Create a Codespace** from this repo (click the button below). 20 | 2. Once the Codespace is running, open the integrated terminal. 21 | 3. Follow the instructions in each **lab** to complete the exercises. 22 | 23 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 24 | 25 | ## Estimated Time 26 | 20 minutes 27 | 28 | ## Initial Configuration Files 29 | 30 | The lab directory contains the following initial files used for the lab - some of which are empty files: 31 | 32 | - `main.tf` 33 | - `variables.tf` 34 | - `providers.tf` 35 | 36 | ## Lab Steps 37 | 38 | ### 1. Configure AWS Credentials 39 | 40 | Set up your AWS credentials as environment variables: 41 | 42 | ```bash 43 | export AWS_ACCESS_KEY_ID="your_access_key" 44 | export AWS_SECRET_ACCESS_KEY="your_secret_key" 45 | ``` 46 | 47 | ### 2. Create a Simple VPC with Join Function 48 | 49 | Create a `main.tf` file with a VPC resource using the join function: 50 | 51 | ```hcl 52 | # Use join function to create a VPC name 53 | resource "aws_vpc" "main" { 54 | cidr_block = "10.0.0.0/16" 55 | enable_dns_hostnames = true 56 | enable_dns_support = true 57 | 58 | tags = { 59 | Name = join("-", [var.environment, "vpc"]) 60 | # This creates "dev-vpc" using the join function 61 | } 62 | } 63 | ``` 64 | 65 | ### 3. Use `min` Function for Subnet Count 66 | 67 | Add subnet resources using the `min` function to determine how many to create: 68 | 69 | ```hcl 70 | # Use min function to determine how many subnets to create 71 | # This ensures we don't try to create more subnets than we have AZs 72 | resource "aws_subnet" "main" { 73 | count = min(length(var.availability_zones), length(var.subnet_cidrs)) 74 | vpc_id = aws_vpc.main.id 75 | cidr_block = var.subnet_cidrs[count.index] 76 | availability_zone = var.availability_zones[count.index] 77 | 78 | tags = { 79 | Name = "${var.environment}-subnet-${count.index + 1}" 80 | } 81 | } 82 | ``` 83 | 84 | ### 4. Use `toset` Function to Remove Duplicates 85 | 86 | Create a security group with tags based on unique team names: 87 | 88 | ```hcl 89 | # Use toset function to remove duplicates from teams list 90 | locals { 91 | unique_teams = toset(var.teams) 92 | } 93 | 94 | # Create security group 95 | resource "aws_security_group" "example" { 96 | name = "${var.environment}-security-group" 97 | description = "Example security group" 98 | vpc_id = aws_vpc.main.id 99 | 100 | tags = { 101 | Name = "${var.environment}-security-group" 102 | Teams = join(", ", local.unique_teams) 103 | # This joins unique team names with commas 104 | } 105 | } 106 | ``` 107 | 108 | ### 5. Add Simple Outputs 109 | 110 | Create an `outputs.tf` file with a few outputs: 111 | 112 | ```hcl 113 | output "vpc_id" { 114 | description = "The ID of the VPC" 115 | value = aws_vpc.main.id 116 | } 117 | 118 | output "subnet_count" { 119 | description = "Number of subnets created (using min function)" 120 | value = min(length(var.availability_zones), length(var.subnet_cidrs)) 121 | } 122 | 123 | output "unique_teams" { 124 | description = "List of unique teams (using toset function)" 125 | value = local.unique_teams 126 | } 127 | 128 | output "security_group_name" { 129 | description = "Security group name (created with join function)" 130 | value = aws_security_group.example.name 131 | } 132 | ``` 133 | 134 | ### 6. Apply the Configuration 135 | 136 | Initialize and apply the configuration: 137 | 138 | ```bash 139 | terraform init 140 | terraform plan 141 | terraform apply 142 | ``` 143 | 144 | Observe how the functions work: 145 | - `join` creates string values by combining elements 146 | - `min` calculates the minimum value between two numbers 147 | - `toset` converts a list to a set, removing duplicates 148 | 149 | ### 7. Clean Up 150 | 151 | Remove all created resources: 152 | 153 | ```bash 154 | terraform destroy 155 | ``` 156 | 157 | ## Function Reference 158 | 159 | ### Join Function 160 | The `join` function combines a list of strings with a specified delimiter. 161 | ``` 162 | join(separator, list) 163 | ``` 164 | Example: `join("-", ["dev", "vpc"])` produces `"dev-vpc"` 165 | 166 | ### Min Function 167 | The `min` function returns the minimum value from a set of numbers. 168 | ``` 169 | min(number1, number2, ...) 170 | ``` 171 | Example: `min(3, 5)` produces `3` 172 | 173 | ### Toset Function 174 | The `toset` function converts a list to a set, removing any duplicate elements. 175 | ``` 176 | toset(list) 177 | ``` 178 | Example: `toset(["a", "b", "a", "c"])` produces `["a", "b", "c"]` -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AZURE/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-11-AZURE: Deploying Resources to Multiple Regions 2 | 3 | ## Overview 4 | This lab demonstrates how to use multiple provider blocks in Terraform to deploy resources to different Azure regions simultaneously. You'll create resources in two regions using a simple, free configuration. 5 | 6 | [![Lab 11](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - Azure free account 13 | - Basic understanding of Terraform and Azure concepts 14 | 15 | Note: Azure credentials are required for this lab. 16 | 17 | ## How to Use This Hands-On Lab 18 | 19 | 1. **Create a Codespace** from this repo (click the button below). 20 | 2. Once the Codespace is running, open the integrated terminal. 21 | 3. Follow the instructions in each **lab** to complete the exercises. 22 | 23 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 24 | 25 | ## Estimated Time 26 | 10 minutes 27 | 28 | ## Existing Configuration Files 29 | 30 | The lab directory contains the following initial files: 31 | 32 | - `variables.tf` 33 | - `providers.tf` 34 | - `main.tf` 35 | 36 | ## Lab Steps 37 | 38 | ### 1. Initialize Terraform 39 | 40 | Initialize your Terraform workspace: 41 | ```bash 42 | terraform init 43 | ``` 44 | 45 | ### 2. Examine the Provider Configuration 46 | 47 | Notice how the `provider` blocks are configured in `providers.tf`: 48 | - The primary provider with an alias of "primary" 49 | - The secondary provider with an alias of "secondary" 50 | 51 | > Note to keep this lab simple, I didn't add any specific configurations to the providers. However, normally you could add unique configurations for each, such as a different subscription_id to deploy resources to a different subscription. This was done because Azure provider doesn't support regions within the provider itself like other providers do. 52 | 53 | ### 3. Examine the Resource Configuration 54 | 55 | Look at how resources specify which provider to use: 56 | - `provider = azurerm.primary` for resources in the primary region 57 | - `provider = azurerm.secondary` for resources in the secondary region 58 | 59 | ### 4. Run Plan and Apply 60 | 61 | Create the resources in both regions: 62 | ```bash 63 | terraform plan 64 | terraform apply 65 | ``` 66 | 67 | ### 5. Add a Container to Each Storage Account 68 | 69 | Add the following resources to `main.tf`: 70 | 71 | ```hcl 72 | # Storage Container in primary region 73 | resource "azurerm_storage_container" "primary" { 74 | name = "data" 75 | storage_account_id = azurerm_storage_account.primary.id 76 | container_access_type = "private" 77 | } 78 | 79 | # Storage Container in secondary region 80 | resource "azurerm_storage_container" "secondary" { 81 | name = "data" 82 | storage_account_id = azurerm_storage_account.secondary.id 83 | container_access_type = "private" 84 | } 85 | ``` 86 | 87 | Add the `provider` configuration to each of the blocks to specify what provider alias to use for each resource: 88 | 89 | ```hcl 90 | # Storage Container in primary region 91 | resource "azurerm_storage_container" "primary" { 92 | provider = azurerm.primary # <--- add this line here 93 | name = "data" 94 | storage_account_id = azurerm_storage_account.primary.id 95 | container_access_type = "private" 96 | } 97 | 98 | # Storage Container in secondary region 99 | resource "azurerm_storage_container" "secondary" { 100 | provider = azurerm.secondary # <--- add this line here 101 | name = "data" 102 | storage_account_id = azurerm_storage_account.secondary.id 103 | container_access_type = "private" 104 | } 105 | ``` 106 | 107 | ### 6. Apply the Changes 108 | 109 | Apply the configuration to create the containers: 110 | ```bash 111 | terraform apply 112 | ``` 113 | 114 | ### 7. Create outputs.tf 115 | 116 | Create an outputs.tf file: 117 | 118 | ```hcl 119 | output "primary_resource_group_name" { 120 | description = "Name of the resource group in the primary region" 121 | value = azurerm_resource_group.primary.name 122 | } 123 | 124 | output "secondary_resource_group_name" { 125 | description = "Name of the resource group in the secondary region" 126 | value = azurerm_resource_group.secondary.name 127 | } 128 | 129 | output "primary_storage_account_name" { 130 | description = "Name of the storage account in the primary region" 131 | value = azurerm_storage_account.primary.name 132 | } 133 | 134 | output "secondary_storage_account_name" { 135 | description = "Name of the storage account in the secondary region" 136 | value = azurerm_storage_account.secondary.name 137 | } 138 | ``` 139 | 140 | ### 8. Apply to See Outputs 141 | ```bash 142 | terraform apply 143 | ``` 144 | 145 | ### 9. Clean Up Resources 146 | 147 | When you're done, clean up all resources: 148 | ```bash 149 | terraform destroy 150 | ``` 151 | 152 | ## Understanding Multiple Provider Configuration 153 | 154 | ### Provider Aliases 155 | - Provider aliases allow you to define multiple configurations for the same provider 156 | - Each provider block can have its own configuration (location, credentials, etc.) 157 | - Use the `alias` attribute to name each provider configuration 158 | 159 | ### Specifying Providers for Resources 160 | - Use the `provider` attribute in resource blocks to specify which provider to use 161 | - Format is `provider = azurerm.` 162 | - If no provider is specified, the default provider (without an alias) is used 163 | 164 | ### Common Multi-Region Scenarios 165 | - Disaster recovery across regions 166 | - Deploying to multiple regions for reduced latency 167 | - Creating resources that need to interact across regions -------------------------------------------------------------------------------- /labs/lab_13_using_terraform_built_in_functions/GITHUB/github.md: -------------------------------------------------------------------------------- 1 | # LAB-13-GH: Using Basic Terraform Functions 2 | 3 | ## Overview 4 | In this lab, you will learn how to use a few essential Terraform built-in functions: `min`, `max`, `join`, and `toset`. These functions help you manipulate values and create more flexible infrastructure configurations. The lab uses GitHub free resources to ensure no costs are incurred. 5 | 6 | [![Lab 13](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account 13 | - GitHub personal access token 14 | - Basic understanding of Terraform and GitHub concepts 15 | 16 | Note: GitHub credentials are required for this lab. 17 | 18 | ## How to Use This Hands-On Lab 19 | 20 | 1. **Create a Codespace** from this repo (click the button below). 21 | 2. Once the Codespace is running, open the integrated terminal. 22 | 3. Follow the instructions in each **lab** to complete the exercises. 23 | 24 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 25 | 26 | ## Estimated Time 27 | 20 minutes 28 | 29 | ## Initial Configuration Files 30 | 31 | - `main.tf` 32 | - `providers.tf` 33 | - `variables.tf` 34 | 35 | ## Lab Steps 36 | 37 | ### 1. Configure GitHub Credentials 38 | 39 | Set up your GitHub personal access token: 40 | 41 | ```bash 42 | export GITHUB_TOKEN="your_personal_access_token" 43 | ``` 44 | 45 | ### 2. Create Repositories with Join Function 46 | 47 | Create a `main.tf` file with repository resources using the join function: 48 | 49 | ```hcl 50 | # Use join function to create repository names 51 | resource "github_repository" "main" { 52 | count = min(3, length(var.repository_names)) 53 | name = join("-", [var.environment, var.repository_names[count.index]]) 54 | description = "Repository for ${var.repository_names[count.index]}" 55 | visibility = "public" 56 | auto_init = true 57 | 58 | # This creates repos like "dev-api", "dev-web", etc. using the join function 59 | } 60 | ``` 61 | 62 | ### 3. View details of the `Min` Function for Repository Count 63 | 64 | The code above already uses the `min` function to limit the number of repositories to create: 65 | ```hcl 66 | count = min(3, length(var.repository_names)) 67 | ``` 68 | 69 | This ensures that no more than `3` repositories are created, even if the variable contains more names. 70 | 71 | ### 4. Use Toset Function to Remove Duplicates 72 | 73 | Create a tearepo for each unique members by converting the `var.team_members` **list** to a **set**: 74 | 75 | ```hcl 76 | # Use toset function to remove duplicates from team members list 77 | locals { 78 | unique_members = toset(var.team_members) 79 | } 80 | 81 | resource "github_repository" "user_repo" { 82 | for_each = local.unique_members 83 | 84 | name = join("-", [var.environment, each.value]) 85 | description = "Repo for ${each.value} to store code" 86 | visibility = "public" 87 | auto_init = true 88 | } 89 | ``` 90 | 91 | ### 5. Create Repository Topics with Join 92 | 93 | Create repository topics by joining arrays: 94 | 95 | ```hcl 96 | # Use join for topic descriptions 97 | resource "github_repository_file" "readme" { 98 | count = min(3, length(var.repository_names)) 99 | repository = github_repository.main[count.index].name 100 | branch = "main" 101 | file = "README.md" 102 | content = <<-EOT 103 | # ${upper(var.repository_names[count.index])} 104 | 105 | This is the ${var.repository_names[count.index]} repository. 106 | 107 | Topics: ${join(", ", var.topics)} 108 | EOT 109 | commit_message = "Add README with topics" 110 | commit_author = "Terraform" 111 | commit_email = "terraform@example.com" 112 | overwrite_on_create = true 113 | } 114 | ``` 115 | 116 | ### 6. Add Simple Outputs 117 | 118 | Create an `outputs.tf` file with a few outputs: 119 | 120 | ```hcl 121 | output "repository_urls" { 122 | description = "URLs of the created repositories" 123 | value = github_repository.main[*].html_url 124 | } 125 | 126 | output "repository_count" { 127 | description = "Number of repositories created (using min function)" 128 | value = min(3, length(var.repository_names)) 129 | } 130 | 131 | output "unique_team_members" { 132 | description = "List of unique team members (using toset function)" 133 | value = local.unique_members 134 | } 135 | 136 | output "user_repo_urls" { 137 | description = "A map of each user's repo URL" 138 | value = { 139 | for username, repo in github_repository.user_repo : username => repo.html_url 140 | } 141 | } 142 | ``` 143 | 144 | ### 7. Apply the Configuration 145 | 146 | Initialize and apply the configuration: 147 | 148 | ```bash 149 | terraform init 150 | terraform plan 151 | terraform apply 152 | ``` 153 | 154 | Observe how the functions work: 155 | - `join` creates string values by combining elements 156 | - `min` calculates the minimum value between two numbers 157 | - `toset` converts a list to a set, removing duplicates 158 | 159 | ### 8. Clean Up 160 | 161 | Remove all created resources: 162 | 163 | ```bash 164 | terraform destroy 165 | ``` 166 | 167 | ## Function Reference 168 | 169 | ### Join Function 170 | The `join` function combines a list of strings with a specified delimiter. 171 | ``` 172 | join(separator, list) 173 | ``` 174 | Example: `join("-", ["dev", "api"])` produces `"dev-api"` 175 | 176 | ### Min Function 177 | The `min` function returns the minimum value from a set of numbers. 178 | ``` 179 | min(number1, number2, ...) 180 | ``` 181 | Example: `min(3, 5)` produces `3` 182 | 183 | ### Toset Function 184 | The `toset` function converts a list to a set, removing any duplicate elements. 185 | ``` 186 | toset(list) 187 | ``` 188 | Example: `toset(["user1", "user2", "user1"])` produces `["user1", "user2"]` -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/GITHUB/github.md: -------------------------------------------------------------------------------- 1 | # LAB-10-GH: Managing Explicit Dependencies with depends_on 2 | 3 | ## Overview 4 | This lab demonstrates how to use Terraform's `depends_on` meta-argument with GitHub resources. You'll learn when to use explicit dependencies versus relying on implicit dependencies, using free GitHub resources. 5 | 6 | [![Lab 10](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account 13 | - GitHub personal access token 14 | - Basic understanding of Terraform and GitHub concepts 15 | 16 | Note: GitHub credentials are required for this lab. 17 | 18 | ## How to Use This Hands-On Lab 19 | 20 | 1. **Create a Codespace** from this repo (click the button below). 21 | 2. Once the Codespace is running, open the integrated terminal. 22 | 3. Follow the instructions in each **lab** to complete the exercises. 23 | 24 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 25 | 26 | ## Estimated Time 27 | 15 minutes 28 | 29 | ## Existing Configuration Files 30 | 31 | The lab directory contains the following initial files: 32 | 33 | - `main.tf` 34 | - `providers.tf` 35 | - `variables.tf` 36 | 37 | ## Lab Steps 38 | 39 | ### 1. Identify Implicit Dependencies 40 | 41 | Examine the `main.tf` file and identify the implicit dependencies: 42 | - Repository File depends on Repository (via repository attribute - line 13) 43 | - Issue Label depends on Repository (via repository attribute - line 25) 44 | 45 | ### 2. Initialize Terraform 46 | 47 | Set up your GitHub token as an environment variable: 48 | ```bash 49 | export GITHUB_TOKEN="your-personal-access-token" 50 | ``` 51 | 52 | Initialize your Terraform workspace: 53 | ```bash 54 | terraform init 55 | ``` 56 | 57 | ### 3. Run an Initial Plan and Apply 58 | 59 | Create the initial resources: 60 | ```bash 61 | terraform plan 62 | terraform apply 63 | ``` 64 | 65 | Notice how Terraform automatically determines the correct order based on implicit dependencies. It first creates the repository, then creates the other two resources in parallel. 66 | 67 | ### 6. Add Resources with Explicit Dependencies 68 | 69 | Now, add the following resources that require explicit dependencies: 70 | 71 | ```hcl 72 | # Additional Repository Files with explicit dependency 73 | resource "github_repository_file" "contributing" { 74 | repository = github_repository.main.name 75 | branch = var.default_branch 76 | file = "CONTRIBUTING.md" 77 | content = "# Contributing Guidelines\n\nThank you for your interest in contributing to this project." 78 | commit_message = "Add CONTRIBUTING.md" 79 | commit_author = "Terraform" 80 | commit_email = "terraform@example.com" 81 | overwrite_on_create = true 82 | 83 | # Explicitly depend on branch protection rule 84 | # This ensures the branch is protected before adding files 85 | depends_on = [github_repository.main] 86 | } 87 | 88 | # Additional Label with explicit dependency 89 | resource "github_issue_label" "enhancement" { 90 | repository = github_repository.main.name 91 | name = "enhancement" 92 | color = "00FF00" 93 | description = "Enhancement requests" 94 | 95 | # Explicitly depend on the first label and team access 96 | # This ensures labels are created in a specific order 97 | depends_on = [ 98 | github_issue_label.bug 99 | ] 100 | } 101 | ``` 102 | 103 | ### 8. Apply and Observe Order 104 | ```bash 105 | terraform apply 106 | ``` 107 | 108 | Watch how Terraform respects both your implicit and explicit dependencies. 109 | 110 | ### 9. Add Outputs 111 | 112 | Create an outputs.tf file: 113 | 114 | ```hcl 115 | output "repository_name" { 116 | description = "Name of the GitHub repository" 117 | value = github_repository.main.name 118 | } 119 | 120 | output "repository_url" { 121 | description = "URL of the GitHub repository" 122 | value = github_repository.main.html_url 123 | } 124 | 125 | output "files_created" { 126 | description = "Files created in the repository" 127 | value = [ 128 | github_repository_file.readme.file, 129 | github_repository_file.contributing.file 130 | ] 131 | } 132 | 133 | output "dependency_example" { 134 | description = "Example of dependencies in this lab" 135 | value = { 136 | "Implicit dependencies" = "Repository -> Repository File, Repository -> Issue Label, Team -> Team Repository Access" 137 | "Explicit dependencies" = "Label/Team Access -> Enhancement Label, Files" 138 | } 139 | } 140 | ``` 141 | 142 | ### 10. Apply to See Outputs 143 | ```bash 144 | terraform apply 145 | ``` 146 | 147 | ### 11. Clean Up Resources 148 | 149 | When you're done, clean up all resources: 150 | ```bash 151 | terraform destroy 152 | ``` 153 | 154 | ## Understanding depends_on 155 | 156 | ### When to Use depends_on: 157 | 1. When there's no implicit dependency (no reference to another resource's attributes) 158 | 2. When a resource needs to be created after another, even though they don't directly reference each other 159 | 3. When you need to ensure a specific creation order for resources 160 | 161 | ### Examples in GitHub: 162 | - Repository files that should be created after branch protection rules are in place 163 | - Webhooks that should be created after a repository is fully configured 164 | - Labels or other resources that need to be created in a specific order 165 | 166 | ### Syntax: 167 | ```hcl 168 | resource "github_example" "example" { 169 | # ... configuration ... 170 | 171 | depends_on = [ 172 | github_other_resource.name 173 | ] 174 | } 175 | ``` 176 | 177 | ## Additional Exercises 178 | 179 | 1. Add project boards with dependencies on repositories 180 | 2. Create multiple repositories with cross-repository dependencies 181 | 3. Set up organization-level resources with dependencies on team resources 182 | 4. Try creating a dependency chain across different GitHub resource types -------------------------------------------------------------------------------- /labs/lab_01_getting_started_with_terraform/github.md: -------------------------------------------------------------------------------- 1 | # LAB-01-GH: Getting Started with Terraform Configuration with GitHub 2 | 3 | ## Overview 4 | In this lab, you will create your first Terraform configuration for GitHub by setting up the required file structure and implementing the GitHub provider configuration. You'll learn how to format, validate, and initialize a Terraform working directory. 5 | 6 | [![Lab 01](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - VS Code or preferred code editor installed 13 | 14 | Note: GitHub credentials are not required for this lab as we will only be configuring the provider without creating any resources. 15 | 16 | ## How to Use This Hands-On Lab 17 | 18 | 1. **Create a Codespace** from this repo (click the button below). 19 | 2. Once the Codespace is running, open the integrated terminal. 20 | 3. Follow the instructions in each **lab** to complete the exercises. 21 | 22 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 23 | 24 | ## Estimated Time 25 | 15 minutes 26 | 27 | ## Lab Steps 28 | 29 | ### 1. Check Terraform Version 30 | 31 | Determine your installed Terraform version: 32 | 33 | ```bash 34 | terraform version 35 | ``` 36 | 37 | Note this version number as you'll need it for the provider configuration. 38 | 39 | ### 2. Create the Project Structure 40 | 41 | Create a labs directory and a terraform directory within it that will serve as your workspace for these initial labs: 42 | 43 | ```bash 44 | mkdir -p labs/terraform 45 | cd labs/terraform 46 | ``` 47 | 48 | Create the initial configuration files in this directory: 49 | 50 | ```bash 51 | touch main.tf variables.tf providers.tf 52 | ``` 53 | 54 | You can also just create these in VSCode by right-clicking the directory. 55 | 56 | Your directory structure should look like this: 57 | ```bash 58 | labs/ 59 | └── terraform/ 60 | ├── main.tf 61 | ├── providers.tf 62 | └── variables.tf 63 | ``` 64 | 65 | This directory will be your working environment for the upcoming labs as we build our infrastructure incrementally. 66 | 67 | ### 3. Configure the GitHub Provider 68 | 69 | Open `providers.tf` and add the following configuration: 70 | 71 | ```hcl 72 | terraform { 73 | required_version = ">= 1.10.x" # Replace with your installed version 74 | required_providers { 75 | github = { 76 | source = "integrations/github" 77 | version = "~> 6.5.0" 78 | } 79 | } 80 | } 81 | 82 | provider "github" {} 83 | ``` 84 | 85 | ### 4. Format the Configuration 86 | 87 | Run the following command to ensure consistent formatting: 88 | 89 | ```bash 90 | terraform fmt 91 | ``` 92 | 93 | Expected output: If any files were formatted, their names will be listed. If no formatting was needed, there will be no output. 94 | 95 | ### 5. Initialize the Working Directory 96 | Initialize the working directory to prep the environment and download the provider: 97 | 98 | ```bash 99 | terraform init 100 | ``` 101 | 102 | Expected Output: 103 | ```bash 104 | # terraform init 105 | Initializing the backend... 106 | Initializing provider plugins... 107 | - Finding integrations/github versions matching "~> 6.5.0"... 108 | - Installing integrations/github v6.5.0... 109 | - Installed integrations/github v6.5.0 (signed by a HashiCorp partner, key ID 38027F80D7FD5FB2) 110 | Partner and community providers are signed by their developers. 111 | If you'd like to know more about provider signing, you can read about it here: 112 | https://www.terraform.io/docs/cli/plugins/signing.html 113 | Terraform has created a lock file .terraform.lock.hcl to record the provider 114 | ``` 115 | 116 | ### 6. Validate the Configuration 117 | 118 | Run the validation command to check for syntax errors: 119 | 120 | ```bash 121 | terraform validate 122 | ``` 123 | 124 | Expected output: 125 | ``` 126 | Success! The configuration is valid. 127 | ``` 128 | 129 | ### 7. Test Version Constraints 130 | 131 | Let's experiment with version constraints: 132 | 133 | 1. Modify the `required_version` in your provider configuration: 134 | 135 | ```hcl 136 | required_version = ">= 99.0.0" # An intentionally high version 137 | ``` 138 | 139 | 2. Run the terraform initialization command: 140 | 141 | ```bash 142 | terraform init 143 | ``` 144 | 145 | You should see an error message similar to: 146 | ``` 147 | Error: Unsupported Terraform Core version 148 | 149 | This configuration requires Terraform version >= 99.0.0, but the current version 150 | is x.x.x. Please upgrade Terraform to a supported version. 151 | ``` 152 | 153 | 3. Change the version requirement back to your current version: 154 | 155 | ```hcl 156 | required_version = ">= 1.10.x" # Replace with your actual version 157 | ``` 158 | 159 | 4. Run terraform init again: 160 | 161 | ```bash 162 | terraform init 163 | ``` 164 | 165 | Expected output: You should now see success messages indicating proper initialization. 166 | 167 | ## Verification Steps 168 | 169 | After completing the lab, verify your work: 170 | 171 | 1. Your directory structure should look like this: 172 | ``` 173 | labs/ 174 | └── terraform/ 175 | ├── main.tf 176 | ├── providers.tf 177 | ├── variables.tf 178 | ├── .terraform.lock.hcl 179 | └── .terraform/ 180 | ``` 181 | 182 | 2. Verify the following: 183 | - The `.terraform` directory exists after initialization 184 | - The `.terraform.lock.hcl` file has been created 185 | - GitHub provider is listed in the lock file 186 | - No error messages are present from the validate command 187 | - All files are properly formatted 188 | 189 | ## Clean Up 190 | 191 | No clean up is required for this lab as no GitHub resources were created. 192 | 193 | ## Success Criteria 194 | - You have created the required file structure 195 | - All Terraform commands (fmt, validate, init) execute successfully 196 | - You observed and understood the version constraint error 197 | - You successfully fixed the version constraint 198 | - The GitHub provider is properly initialized 199 | - The `.terraform.lock.hcl` file is created -------------------------------------------------------------------------------- /labs/lab_01_getting_started_with_terraform/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-01-AZ: Getting Started with Terraform Configuration with Azure 2 | 3 | ## Overview 4 | In this lab, you will create your first Terraform configuration for Azure by setting up the required file structure and implementing the Azure provider configuration. You'll learn how to format, validate, and initialize a Terraform working directory. 5 | 6 | [![Lab 01](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - VS Code or preferred code editor installed 13 | 14 | Note: Azure credentials are not required for this lab as we will only be configuring the provider without creating any resources. 15 | 16 | ## How to Use This Hands-On Lab 17 | 18 | 1. **Create a Codespace** from this repo (click the button below). 19 | 2. Once the Codespace is running, open the integrated terminal. 20 | 3. Follow the instructions in each **lab** to complete the exercises. 21 | 22 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 23 | 24 | ## Estimated Time 25 | 15 minutes 26 | 27 | ## Lab Steps 28 | 29 | ### 1. Check Terraform Version 30 | 31 | Determine your installed Terraform version: 32 | 33 | ```bash 34 | terraform version 35 | ``` 36 | 37 | Note this version number as you'll need it for the provider configuration. 38 | 39 | ### 2. Create the Project Structure 40 | 41 | Create a labs directory and a terraform directory within it that will serve as your workspace for these initial labs: 42 | 43 | ```bash 44 | mkdir -p labs/terraform 45 | cd labs/terraform 46 | ``` 47 | 48 | Create the initial configuration files in this directory: 49 | 50 | ```bash 51 | touch main.tf variables.tf providers.tf 52 | ``` 53 | 54 | You can also just create these in VSCode by right-clicking the directory. 55 | 56 | Your directory structure should look like this: 57 | ```bash 58 | labs/ 59 | └── terraform/ 60 | ├── main.tf 61 | ├── providers.tf 62 | └── variables.tf 63 | ``` 64 | 65 | This directory will be your working environment for the upcoming labs as we build our infrastructure incrementally. 66 | 67 | ### 3. Configure the Azure Provider 68 | 69 | Open `providers.tf` and add the following configuration: 70 | 71 | ```hcl 72 | terraform { 73 | required_version = ">= 1.10.x" # Replace with your installed version 74 | required_providers { 75 | azurerm = { 76 | source = "hashicorp/azurerm" 77 | version = "~> 4.0" 78 | } 79 | } 80 | } 81 | 82 | provider "azurerm" { 83 | features {} 84 | } 85 | ``` 86 | 87 | ### 4. Format the Configuration 88 | 89 | Run the following command to ensure consistent formatting: 90 | 91 | ```bash 92 | terraform fmt 93 | ``` 94 | 95 | Expected output: If any files were formatted, their names will be listed. If no formatting was needed, there will be no output. 96 | 97 | ### 5. Initialize the Working Directory 98 | Initialize the working directory to prep the environment and download the provider: 99 | 100 | ```bash 101 | terraform init 102 | ``` 103 | 104 | Expected output: 105 | ```bash 106 | ➜ terraform git:(main) ✗ terraform init 107 | Initializing the backend... 108 | Initializing provider plugins... 109 | - Finding hashicorp/azurerm versions matching "~> 3.0"... 110 | - Installing hashicorp/azurerm v3.117.0... 111 | - Installed hashicorp/azurerm v3.117.0 (signed by HashiCorp) 112 | Terraform has created a lock file .terraform.lock.hcl to record the provider 113 | selections it made above. Include this file in your version control repository 114 | so that Terraform can guarantee to make the same selections by default when 115 | you run "terraform init" in the future. 116 | 117 | Terraform has been successfully initialized! 118 | ``` 119 | 120 | ### 6. Validate the Configuration 121 | 122 | Run the validation command to check for syntax errors: 123 | 124 | ```bash 125 | terraform validate 126 | ``` 127 | 128 | Expected output: 129 | ```bash 130 | Success! The configuration is valid. 131 | ``` 132 | 133 | ### 7. Test Version Constraints 134 | 135 | Let's experiment with version constraints: 136 | 137 | 1. Modify the `required_version` in your provider configuration: 138 | 139 | ```hcl 140 | required_version = ">= 99.0.0" # An intentionally high version 141 | ``` 142 | 143 | 2. Run the terraform initialization command: 144 | 145 | ```bash 146 | terraform init 147 | ``` 148 | 149 | You should see an error message similar to: 150 | ```bash 151 | Error: Unsupported Terraform Core version 152 | 153 | This configuration requires Terraform version >= 99.0.0, but the current version 154 | is x.x.x. Please upgrade Terraform to a supported version. 155 | ``` 156 | 157 | 3. Change the version requirement back to your current version: 158 | 159 | ```hcl 160 | required_version = ">= 1.10.x" # Replace with your actual version 161 | ``` 162 | 163 | 4. Run terraform init again: 164 | 165 | ```bash 166 | terraform init 167 | ``` 168 | 169 | Expected output: You should now see success messages indicating proper initialization. 170 | 171 | ## Verification Steps 172 | 173 | After completing the lab, verify your work: 174 | 175 | 1. Your directory structure should look like this: 176 | ``` 177 | terraform-lab-01-az/ 178 | ├── main.tf 179 | ├── providers.tf 180 | ├── variables.tf 181 | ├── .terraform/ 182 | └── .terraform.lock.hcl 183 | ``` 184 | 185 | 2. Verify the following: 186 | - The `.terraform` directory exists after initialization 187 | - The `.terraform.lock.hcl` file has been created 188 | - Azure provider is listed in the lock file 189 | - No error messages are present from the validate command 190 | - All files are properly formatted 191 | 192 | ## Clean Up 193 | 194 | No clean up is required for this lab as no Azure resources were created. 195 | 196 | ## Success Criteria 197 | - You have created the required file structure 198 | - All Terraform commands (fmt, validate, init) execute successfully 199 | - You observed and understood the version constraint error 200 | - You successfully fixed the version constraint 201 | - The Azure provider is properly initialized 202 | - The `.terraform.lock.hcl` file is created -------------------------------------------------------------------------------- /labs/lab_01_getting_started_with_terraform/aws.md: -------------------------------------------------------------------------------- 1 | # LAB-01-AWS: Getting Started with Terraform Configuration with AWS 2 | 3 | ## Overview 4 | In this lab, you will create your first Terraform configuration for AWS by setting up the required file structure and implementing the AWS provider configuration. You'll learn how to format, validate, and initialize a Terraform working directory. 5 | 6 | [![Lab 01](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml/badge.svg?branch=main&event=push&job=lab_01)](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - VS Code or preferred code editor installed 13 | 14 | Note: AWS credentials are not required for this lab as we will only be configuring the provider without creating any resources. 15 | 16 | ## How to Use This Hands-On Lab 17 | 18 | 1. **Create a Codespace** from this repo (click the button below). 19 | 2. Once the Codespace is running, open the integrated terminal. 20 | 3. Follow the instructions in each **lab** to complete the exercises. 21 | 22 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 23 | 24 | ## Estimated Time 25 | 15 minutes 26 | 27 | ## Testing 28 | ![testing](https://img.shields.io/badge/Passing-Terraform_1.11.2-purple) 29 | 30 | ## Lab Steps 31 | 32 | ### 1. Check Terraform Version 33 | 34 | Determine your installed Terraform version: 35 | 36 | ```bash 37 | terraform version 38 | ``` 39 | 40 | Note this version number as you'll need it for the provider configuration. 41 | 42 | ### 2. Create the Project Structure 43 | 44 | Create a labs directory and a terraform directory within it that will serve as your workspace for these initial labs: 45 | 46 | ```bash 47 | mkdir -p labs/terraform 48 | cd labs/terraform 49 | ``` 50 | 51 | Create the initial configuration files in this directory: 52 | 53 | ```bash 54 | touch main.tf variables.tf providers.tf 55 | ``` 56 | You can also just create these in VSCode by right-clicking the directory. 57 | 58 | Your directory structure should look like this: 59 | ``` 60 | labs/ 61 | └── terraform/ 62 | ├── main.tf 63 | ├── providers.tf 64 | └── variables.tf 65 | ``` 66 | 67 | This directory will be your working environment for the upcoming labs as we build our infrastructure incrementally. 68 | 69 | 70 | ### 3. Configure the AWS Provider 71 | 72 | Open `providers.tf` and add the following configuration: 73 | 74 | ```hcl 75 | terraform { 76 | required_version = ">= 1.10.x" # Replace with your installed version 77 | required_providers { 78 | aws = { 79 | source = "hashicorp/aws" 80 | version = "~> 5.0" 81 | } 82 | } 83 | } 84 | 85 | provider "aws" { 86 | region = "us-east-1" 87 | } 88 | ``` 89 | 90 | ### 4. Format the Configuration 91 | 92 | Run the following command to ensure consistent formatting: 93 | 94 | ```bash 95 | terraform fmt 96 | ``` 97 | 98 | Expected output: If any files were formatted, their names will be listed. If no formatting was needed, there will be no output. 99 | 100 | ### 5. Initialize the Working Directory 101 | Initialize the working directory to prep the environment and download the provider: 102 | 103 | ```bash 104 | terraform init 105 | ``` 106 | 107 | Expected output: 108 | ```bash 109 | Initializing the backend... 110 | Initializing provider plugins... 111 | - Finding hashicorp/aws versions matching "~> 5.0"... 112 | - Installing hashicorp/aws v5.87.0... 113 | - Installed hashicorp/aws v5.87.0 (signed by HashiCorp) 114 | Terraform has created a lock file .terraform.lock.hcl to record the provider 115 | selections it made above. Include this file in your version control repository 116 | so that Terraform can guarantee to make the same selections by default when 117 | you run "terraform init" in the future. 118 | 119 | 120 | Terraform has been successfully initialized! 121 | ``` 122 | 123 | ### 6. Validate the Configuration 124 | 125 | Run the validation command to check for syntax errors: 126 | 127 | ```bash 128 | terraform validate 129 | ``` 130 | 131 | Expected output: 132 | ``` 133 | Success! The configuration is valid. 134 | ``` 135 | 136 | ### 7. Test Version Constraints 137 | 138 | Let's experiment with version constraints: 139 | 140 | 1. Modify the `required_version` in your provider configuration: 141 | 142 | ```hcl 143 | required_version = ">= 99.0.0" # An intentionally high version 144 | ``` 145 | 146 | 2. Run the terraform initialization command: 147 | 148 | ```bash 149 | terraform init 150 | ``` 151 | 152 | You should see an error message similar to: 153 | ``` 154 | Initializing the backend... 155 | ╷ 156 | │ Error: Unsupported Terraform Core version 157 | │ 158 | │ on providers.tf line 2, in terraform: 159 | │ 2: required_version = ">= 99.0.0" # Replace with your installed version 160 | ``` 161 | 162 | 3. Change the version requirement back to your current version: 163 | 164 | ```hcl 165 | required_version = ">= 1.10.x" # Replace with your actual version 166 | ``` 167 | 168 | 4. Run terraform init again: 169 | 170 | ```bash 171 | terraform init 172 | ``` 173 | 174 | Expected output: You should now see success messages indicating proper initialization. 175 | 176 | ## Verification Steps 177 | 178 | After completing the lab, verify your work: 179 | 180 | 1. Your directory structure should look like this: 181 | ```bash 182 | labs/ 183 | └── terraform/ 184 | ├── .terraform/ 185 | ├── .terraform.lock.hcl 186 | ├── main.tf 187 | ├── providers.tf 188 | └── variables.tf 189 | ``` 190 | 191 | 2. Verify the following: 192 | - The `.terraform` directory exists after initialization 193 | - The `.terraform.lock.hcl` file has been created 194 | - AWS provider is listed in the lock file 195 | - No error messages are present from the validate command 196 | - All files are properly formatted 197 | 198 | ## Clean Up 199 | 200 | No clean up is required for this lab as no AWS resources were created. 201 | 202 | ## Success Criteria 203 | - You have created the required file structure 204 | - All Terraform commands (fmt, validate, init) execute successfully 205 | - You observed and understood the version constraint error 206 | - You successfully fixed the version constraint 207 | - The AWS provider is properly initialized 208 | - The `.terraform.lock.hcl` file is created -------------------------------------------------------------------------------- /labs/lab_12_managing_resource_lifecycle_using_lifecyle/AZURE/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-12-AZURE: Managing Resource Lifecycles with lifecycle Meta-Argument 2 | 3 | ## Overview 4 | This lab demonstrates how to use Terraform's `lifecycle` meta-argument to control the creation, update, and deletion behavior of Azure resources. You'll learn how to prevent resource destruction, create resources before destroying old ones, and ignore specific changes. 5 | 6 | [![Lab 12](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - Azure free account 13 | - Basic understanding of Terraform and Azure concepts 14 | 15 | Note: Azure credentials are required for this lab. 16 | 17 | ## How to Use This Hands-On Lab 18 | 19 | 1. **Create a Codespace** from this repo (click the button below). 20 | 2. Once the Codespace is running, open the integrated terminal. 21 | 3. Follow the instructions in each **lab** to complete the exercises. 22 | 23 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 24 | 25 | ## Estimated Time 26 | 15 minutes 27 | 28 | ## Existing Configuration Files 29 | 30 | The lab directory contains the following initial files: 31 | 32 | - `variables.tf` 33 | - `providers.tf` 34 | - `main.tf` 35 | 36 | 37 | ## Lab Steps 38 | 39 | ### 1. Initialize Terraform 40 | 41 | Initialize your Terraform workspace: 42 | ```bash 43 | terraform init 44 | ``` 45 | 46 | ### 2. Examine the Initial Configuration 47 | 48 | Notice the resources in `main.tf` do not have any **lifecycle** configuration. 49 | 50 | ### 3. Run an Initial Apply 51 | 52 | Create the initial resources: 53 | ```bash 54 | terraform plan 55 | terraform apply 56 | ``` 57 | 58 | ### 4. Add prevent_destroy Lifecycle Configuration 59 | 60 | Add a new resource group configuration: 61 | 62 | ```hcl 63 | # Resource Group with prevent_destroy 64 | resource "azurerm_resource_group" "protected" { 65 | name = "rg-protected-${var.environment}" 66 | location = var.location 67 | 68 | tags = { 69 | Environment = var.environment 70 | Purpose = "Protected" 71 | } 72 | } 73 | ``` 74 | 75 | Modify the resource block and add the `prevent_destroy` lifecycle configuration: 76 | 77 | ```hcl 78 | # Resource Group with prevent_destroy 79 | resource "azurerm_resource_group" "protected" { 80 | name = "rg-protected-${var.environment}" 81 | location = var.location 82 | 83 | tags = { 84 | Environment = var.environment 85 | Purpose = "Protected" 86 | } 87 | 88 | lifecycle { # <--- add this lifecycle block here (all 3 lines) 89 | prevent_destroy = true 90 | } 91 | } 92 | ``` 93 | ### 5. Apply the Changes 94 | 95 | Apply the configuration to create the protected resource group: 96 | ```bash 97 | terraform apply 98 | ``` 99 | 100 | ### 6. Try to Destroy the Protected Resource Group 101 | 102 | Run the command `terraform destroy -target="azurerm_resource_group.protected"` to destroy ONLY the new resource group. 103 | 104 | Terraform should prevent you from destroying the protected resource group. You should get a similar error as shown below: 105 | 106 | ```bash 107 | Error: Instance cannot be destroyed 108 | │ 109 | │ on main.tf line 27: 110 | │ 27: resource "azurerm_resource_group" "protected" { 111 | │ 112 | │ Resource azurerm_resource_group.protected has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the 113 | │ scope of the plan using the -target option. 114 | ``` 115 | 116 | ### 7. Use the `create_before_destroy` Lifecycle Configuration 117 | 118 | Add a storage account with the `create_before_destroy` lifecycle configuration: 119 | 120 | ```hcl 121 | # Storage Account with create_before_destroy 122 | resource "azurerm_storage_account" "replacement" { 123 | name = "replacesa${formatdate("YYMMDD", timestamp())}" 124 | resource_group_name = azurerm_resource_group.standard.name 125 | location = azurerm_resource_group.standard.location 126 | account_tier = "Standard" 127 | account_replication_type = "LRS" 128 | 129 | tags = { 130 | Environment = var.environment 131 | Purpose = "Replacement" 132 | } 133 | 134 | lifecycle { 135 | create_before_destroy = true 136 | } 137 | } 138 | ``` 139 | 140 | ### 8. Apply to Create the Replacement Storage Account 141 | ```bash 142 | terraform apply 143 | ``` 144 | 145 | ### 13. Clean Up Resources 146 | 147 | When you're done, remove the `prevent_destroy` lifecycle setting from the protected resource group first: 148 | 149 | ```hcl 150 | # Resource Group with prevent_destroy removed 151 | resource "azurerm_resource_group" "protected" { 152 | name = "rg-protected-${var.environment}" 153 | location = var.location 154 | 155 | tags = { 156 | Environment = var.environment 157 | Purpose = "Protected" 158 | } 159 | 160 | # Lifecycle block removed or modified 161 | } 162 | ``` 163 | 164 | Then clean up all resources: 165 | ```bash 166 | terraform apply # Apply the removal of prevent_destroy first 167 | terraform destroy 168 | ``` 169 | 170 | ## Understanding the lifecycle Meta-Argument 171 | 172 | ### prevent_destroy 173 | - Prevents Terraform from destroying the resource 174 | - Useful for protecting critical resources like databases, production environments 175 | - Must be removed before you can destroy the resource 176 | 177 | ### create_before_destroy 178 | - Creates the replacement resource before destroying the existing one 179 | - Useful for minimizing downtime during replacements 180 | - Works well for resources that can exist in parallel temporarily 181 | 182 | ### ignore_changes 183 | - Tells Terraform to ignore changes to specific attributes 184 | - Useful when attributes are modified outside of Terraform 185 | - Can be applied to specific attributes or all attributes with `ignore_changes = all` 186 | 187 | ### Syntax: 188 | ```hcl 189 | resource "azurerm_example" "example" { 190 | # ... configuration ... 191 | 192 | lifecycle { 193 | prevent_destroy = true 194 | create_before_destroy = true 195 | ignore_changes = [ 196 | tags, 197 | attribute_name 198 | ] 199 | } 200 | } 201 | ``` -------------------------------------------------------------------------------- /labs/lab_11_using_multiple_providers_for_mulitple_regions/AWS/aws.md: -------------------------------------------------------------------------------- 1 | # LAB-11-AWS: Deploying Resources to Multiple Regions 2 | 3 | ## Overview 4 | This lab demonstrates how to use multiple provider blocks in Terraform to deploy resources to different AWS regions simultaneously. You'll create resources in two regions using a simple, free configuration. 5 | 6 | [![Lab 11](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - AWS free tier account 13 | - Basic understanding of Terraform and AWS concepts 14 | 15 | Note: AWS credentials are required for this lab. 16 | 17 | ## How to Use This Hands-On Lab 18 | 19 | 1. **Create a Codespace** from this repo (click the button below). 20 | 2. Once the Codespace is running, open the integrated terminal. 21 | 3. Follow the instructions in each **lab** to complete the exercises. 22 | 23 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 24 | 25 | ## Estimated Time 26 | 10 minutes 27 | 28 | ## Existing Configuration Files 29 | 30 | The lab directory contains the following initial files that will be used for the lab: 31 | 32 | - `main.tf` 33 | - `variables.tf` 34 | - `providers.tf` 35 | 36 | ## Lab Steps 37 | 38 | ### 1. Initialize Terraform 39 | 40 | Validate you are in the correct directory: 41 | 42 | ```bash 43 | cd /workspaces/terraform-codespaces/labs/lab_11_using_multiple_providers_for_mulitple_regions/AWS 44 | ``` 45 | 46 | Initialize your Terraform workspace: 47 | ```bash 48 | terraform init 49 | ``` 50 | 51 | ### 2. Examine the Provider Configuration 52 | 53 | Notice how the provider blocks are configured in providers.tf: 54 | - The primary provider with an alias of `primary` 55 | - The secondary provider with an alias of `secondary` 56 | 57 | ### 3. Examine the Resource Configuration 58 | 59 | Look at how resources specify which provider to use: 60 | - `provider = aws.primary` for resources in the primary region 61 | - `provider = aws.secondary` for resources in the secondary region 62 | 63 | > Note: Feel free to change the values of the variables `primary_region` and `secondary_region` to your local regions. 64 | 65 | ### 4. Run Plan and Apply 66 | 67 | Create the resources in both regions: 68 | ```bash 69 | terraform plan 70 | terraform apply 71 | ``` 72 | 73 | > Feel free to browse the AWS Console (UI) to see that resources were deployed across two different regions. Specifically for Amazon S3, you don't need to change the region to see all of your buckets across different regions. 74 | 75 | ### 5. Add SNS Topics to Both Regions 76 | 77 | Add the following resources to `main.tf`: 78 | 79 | ```hcl 80 | # SNS Topic in primary region 81 | resource "aws_sns_topic" "primary" { 82 | provider = aws.primary 83 | name = "primary-${var.environment}-topic" 84 | 85 | tags = { 86 | Name = "Primary Region Topic" 87 | Environment = var.environment 88 | Region = var.primary_region 89 | } 90 | } 91 | 92 | # SNS Topic in secondary region 93 | resource "aws_sns_topic" "secondary" { 94 | provider = aws.secondary 95 | name = "secondary-${var.environment}-topic" 96 | 97 | tags = { 98 | Name = "Secondary Region Topic" 99 | Environment = var.environment 100 | Region = var.secondary_region 101 | } 102 | } 103 | ``` 104 | 105 | ### 6. Apply the Changes 106 | 107 | Apply the configuration to create the SNS topics: 108 | ```bash 109 | terraform apply 110 | ``` 111 | 112 | > Feel free to browse the AWS Console (UI) to see that resources were deployed across two different regions. For SNS topics, you'll need to change the region in the top right to see the resource in each respective region. 113 | 114 | 115 | ### 7. Create `outputs.tf` file to see information about the resources 116 | 117 | Create an `outputs.tf` file: 118 | 119 | ```hcl 120 | output "primary_bucket_name" { 121 | description = "Name of the S3 bucket in the primary region" 122 | value = aws_s3_bucket.primary.bucket 123 | } 124 | 125 | output "secondary_bucket_name" { 126 | description = "Name of the S3 bucket in the secondary region" 127 | value = aws_s3_bucket.secondary.bucket 128 | } 129 | 130 | output "primary_bucket_region" { 131 | description = "Region of the primary S3 bucket" 132 | value = aws_s3_bucket.primary.region 133 | } 134 | 135 | output "secondary_bucket_region" { 136 | description = "Region of the secondary S3 bucket" 137 | value = aws_s3_bucket.secondary.region 138 | } 139 | 140 | output "primary_sns_topic_arn" { 141 | description = "ARN of the SNS topic in the primary region" 142 | value = aws_sns_topic.primary.arn 143 | } 144 | 145 | output "secondary_sns_topic_arn" { 146 | description = "ARN of the SNS topic in the secondary region" 147 | value = aws_sns_topic.secondary.arn 148 | } 149 | ``` 150 | 151 | ### 8. Apply to See Outputs 152 | ```bash 153 | terraform apply 154 | ``` 155 | 156 | Take a look at the outputs. Notice that the SNS topics are in two different regions (based on the ARN). That proves that the topics were deployed in two different regions but within the same Terraform configuration. 157 | 158 | ### 9. Clean Up Resources 159 | 160 | When you are done, clean up all resources: 161 | 162 | ```bash 163 | terraform destroy 164 | ``` 165 | 166 | ## Understanding Multiple Provider Configuration 167 | 168 | ### Provider Aliases 169 | - Provider aliases allow you to define multiple configurations for the same provider 170 | - Each provider block can have its own configuration (region, credentials, etc.) 171 | - Use the `alias` attribute to name each provider configuration 172 | 173 | ### Specifying Providers for Resources 174 | - Use the `provider` attribute in resource blocks to specify which provider to use 175 | - Format is `provider = aws.` 176 | - If no provider is specified, the default provider (without an alias) is used 177 | 178 | ### Common Multi-Region Scenarios 179 | - Disaster recovery across regions 180 | - Deploying to multiple regions for reduced latency 181 | - Creating resources that interact across regions 182 | 183 | ## Additional Exercises 184 | 185 | 1. Add a DynamoDB table to each region 186 | 2. Create a CloudWatch Log Group in each region 187 | 3. Create resources in a third region using an additional provider block -------------------------------------------------------------------------------- /labs/lab_14_using_the_terraform_registry/GITHUB/github.md: -------------------------------------------------------------------------------- 1 | # LAB-14-GH: Using Terraform Registry Modules with GitHub 2 | 3 | ## Overview 4 | In this lab, you will learn how to use modules from the Terraform Registry to create GitHub infrastructure more efficiently. You'll use two different modules, with one module using the output from another. You'll also call the same module multiple times with different parameters to create similar but unique resources. The lab uses GitHub free resources to ensure no costs are incurred. 5 | 6 | [![Lab 14](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account 13 | - GitHub personal access token 14 | - Basic understanding of Terraform and GitHub concepts 15 | 16 | Note: GitHub credentials are required for this lab. 17 | 18 | ## How to Use This Hands-On Lab 19 | 20 | 1. **Create a Codespace** from this repo (click the button below). 21 | 2. Once the Codespace is running, open the integrated terminal. 22 | 3. Follow the instructions in each **lab** to complete the exercises. 23 | 24 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 25 | 26 | ## Estimated Time 27 | 25 minutes 28 | 29 | ## Initial Configuration Files 30 | 31 | - `providers.tf` 32 | - `variables.tf` 33 | 34 | ## Lab Steps 35 | 36 | ### 1. Configure GitHub Credentials 37 | 38 | Set up your GitHub personal access token: 39 | 40 | ```bash 41 | export GITHUB_TOKEN="your_personal_access_token" 42 | ``` 43 | 44 | ### 2. Use the GitHub Repository Module from Terraform Registry 45 | 46 | Add the following to the `main.tf` file and use the GitHub Repository module: 47 | 48 | ```hcl 49 | module "repository" { 50 | source = "mineiros-io/repository/github" 51 | version = "0.18.0" 52 | 53 | name = "${var.environment}-app" 54 | description = "Application repository for ${var.environment} environment" 55 | topics = ["terraform", "application", var.environment] 56 | visibility = var.repository_visibility 57 | archived = false 58 | } 59 | ``` 60 | 61 | ### 3. Use the GitHub Branch Protection with Repository Output 62 | 63 | Add the GitHub Branch Protection module, using the repository output from the first module: 64 | 65 | ```hcl 66 | module "default-branch-protection" { 67 | source = "masterborn/default-branch-protection/github" 68 | version = "1.1.0" 69 | 70 | repository_name = split("/", module.repository.full_name)[1] 71 | } 72 | ``` 73 | 74 | ### 4. Create Multiple Repositories Using For_Each 75 | 76 | Call the repository module again with different parameters to create another repo: 77 | 78 | ```hcl 79 | module "user-repositories" { 80 | source = "mineiros-io/repository/github" 81 | version = "0.18.0" 82 | 83 | for_each = var.user_repos 84 | name = "${each.value}-${var.environment}-app" 85 | description = "Application repository for ${var.environment} environment" 86 | topics = ["terraform", "application", var.environment] 87 | visibility = var.repository_visibility 88 | archived = false 89 | } 90 | ``` 91 | 92 | ### 5. Configure Branch Protection for the User's Repository 93 | 94 | Set up branch protection for the second repository: 95 | 96 | ```hcl 97 | module "user-default-branch-protection" { 98 | source = "masterborn/default-branch-protection/github" 99 | version = "1.1.0" 100 | 101 | for_each = var.user_repos 102 | repository_name = split("/", module.user-repositories[each.key].full_name)[1] 103 | } 104 | ``` 105 | 106 | ### 6. Initialize and Apply 107 | 108 | Initialize and apply the configuration: 109 | 110 | ```bash 111 | terraform init 112 | ``` 113 | 114 | > Notice how Terraform downloads the modules locally so it can now use them to create our resources. 115 | 116 | Run a plan and apply: 117 | 118 | ```bash 119 | terraform plan 120 | terraform apply 121 | ``` 122 | 123 | Notice how Terraform: 124 | - Downloads the modules from the Terraform Registry 125 | - Creates repositories using the GitHub repository module 126 | - Applies branch protection to each repository 127 | - Creates multiple repositories using for_each 128 | 129 | ### 7. Clean Up 130 | 131 | Remove all created resources: 132 | 133 | ```bash 134 | terraform destroy 135 | ``` 136 | 137 | ## Understanding Module Usage 138 | 139 | Let's examine the key aspects of using modules from the Terraform Registry: 140 | 141 | ### Module Sources 142 | The `source` attribute specifies where to find the module: 143 | ```hcl 144 | source = "mineiros-io/repository/github" 145 | ``` 146 | This format refers to modules in the public Terraform Registry. 147 | 148 | ### Module Versioning 149 | The `version = "1.1.0"` portion pins the module to a specific version when using Git sources. 150 | This ensures consistent behavior even if the module is updated. 151 | 152 | ### Module Inputs 153 | Each module accepts input variables that control its behavior: 154 | ```hcl 155 | name = "${var.environment}-app" 156 | ``` 157 | 158 | ### Module Outputs 159 | Modules provide outputs that can be used by other resources: 160 | ```hcl 161 | repository_name = module.repository.repository_name 162 | ``` 163 | Here, the repository name output from the first module is used as an input for the second module. 164 | 165 | ### Multiple Module Instances 166 | The same module can be called multiple times with different parameters: 167 | ```hcl 168 | module "repository" { 169 | name = "${var.environment}-app" 170 | ... 171 | } 172 | 173 | module "repository_xyz" { 174 | name = "${var.environment}-app-xyz-repo" 175 | ... 176 | } 177 | ``` 178 | 179 | ### Using For_Each with Modules 180 | Modules can be instantiated multiple times using for_each: 181 | ```hcl 182 | module "multiple_repositories" { 183 | for_each = var.additional_repositories 184 | name = "${var.environment}-${each.key}" 185 | ... 186 | } 187 | ``` 188 | This creates one module instance for each element in the map, with each instance receiving different input values. 189 | 190 | ## Additional Resources 191 | 192 | - [Terraform Registry](https://registry.terraform.io/) 193 | - [HappyPathway GitHub Repository Module](https://registry.terraform.io/modules/HappyPathway/repo/github/latest) 194 | - [Masterborn GitHub Branch Protection Module](https://github.com/masterborn/terraform-github-default-branch-protection) -------------------------------------------------------------------------------- /labs/lab_04_managing_mulitple_resources/github.md: -------------------------------------------------------------------------------- 1 | # LAB-04-GH: Managing Multiple Resources and Dependencies 2 | 3 | ## Overview 4 | In this lab, you will expand your GitHub repository configuration by adding multiple interconnected resources. You'll learn how Terraform manages dependencies between resources and how to structure more complex configurations. We'll create repositories, manage repository configures, and implement branch protection rules, demonstrating how different GitHub resources work together. 5 | 6 | [![Lab 04](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account with appropriate permissions 13 | - GitHub organization admin access 14 | - Completion of [LAB-03-GH(https://github.com/btkrausen/terraform-codespaces/blob/main/labs/lab_03_working_with_variables_and_dependencies/github.md)] with existing repository configuration 15 | 16 | ## Estimated Time 17 | 30 minutes 18 | 19 | ## Lab Steps 20 | 21 | **Note**: To fully appreciate this lab and understand how the dependencies work, I recommend typing out the code rather just than copying and pasting. 22 | 23 | ### 1. Add New Variable Definitions 24 | 25 | Add the following to your existing `variables.tf`: 26 | 27 | ```hcl 28 | # Development Repo Variables 29 | variable "dev_repository_name" { 30 | description = "Name of the Dev GitHub repository" 31 | type = string 32 | default = "development-repo" 33 | } 34 | 35 | variable "dev_repo_issues" { 36 | description = "Dev repo issues settings" 37 | type = bool 38 | default = true 39 | } 40 | 41 | variable "dev_discussions" { 42 | description = "Dev repo discussions settings" 43 | type = bool 44 | default = true 45 | } 46 | 47 | variable "dev_wiki" { 48 | description = "Dev repo wiki settings" 49 | type = bool 50 | default = true 51 | } 52 | ``` 53 | 54 | ### 2. Configure Team Repository Access 55 | 56 | Add new configurations for the new development repository in `main.tf`: 57 | 58 | ```hcl 59 | # Create development repository 60 | resource "github_repository" "development" { 61 | name = var.dev_repository_name 62 | description = "Primary Dev Repo for new apps" 63 | visibility = "public" 64 | 65 | auto_init = true 66 | 67 | has_issues = var.dev_repo_issues 68 | has_discussions = var.dev_discussions 69 | has_wiki = var.dev_wiki 70 | 71 | allow_merge_commit = true 72 | allow_squash_merge = true 73 | allow_rebase_merge = true 74 | 75 | topics = ["terraform", "infrastructure-as-code"] 76 | } 77 | ``` 78 | 79 | ### 3. Create Development Configuration Options 80 | 81 | Add new configurations for related settings for the development repository in `main.tf`: 82 | 83 | ```hcl 84 | resource "github_branch_protection" "development" { 85 | repository_id = github_repository.development.node_id 86 | pattern = "main" 87 | } 88 | 89 | resource "github_branch" "development" { 90 | repository = github_repository.development.name 91 | branch = "main" 92 | } 93 | 94 | resource "github_branch_default" "development" { 95 | repository = github_repository.development.name 96 | branch = github_branch.development.branch 97 | } 98 | ``` 99 | 100 | ### 4. Add a Repository File (.gitignore) 101 | 102 | Create a CODEOWNERS file in the repository: 103 | 104 | ```hcl 105 | # Repository Files 106 | resource "github_repository_file" "development" { 107 | repository = github_repository.development.name 108 | branch = github_branch.development.branch 109 | file = ".gitignore" 110 | content = "**/*.tfstate" 111 | commit_message = "Managed by Terraform" 112 | commit_author = "Terraform User" 113 | commit_email = "terraform@course.com" 114 | overwrite_on_create = true 115 | } 116 | ``` 117 | 118 | > Note the explicit dependency on the branch protection rule to ensure the file can be created after the branch is protected. 119 | 120 | ### 5. Add New Outputs 121 | 122 | Add the following output block to your `outputs.tf` file to see information about the newly created repository: 123 | 124 | ```hcl 125 | output "development_repo" { 126 | description = "The name of the development repo" 127 | value = github_repository.development.name 128 | } 129 | ``` 130 | 131 | ### 6. Update terraform.tfvars 132 | 133 | Add the team values to your existing `terraform.tfvars`: 134 | 135 | ```hcl 136 | # Development Repo Configurations 137 | dev_repository_name = "development-repo" 138 | dev_repo_issues = true 139 | dev_wiki = true 140 | dev_discussions = false 141 | ``` 142 | 143 | ### 7. Apply the Configuration 144 | 145 | Run the following commands: 146 | ```bash 147 | terraform fmt 148 | terraform validate 149 | terraform plan 150 | terraform apply 151 | ``` 152 | 153 | Review the proposed changes and type `yes` when prompted to confirm. 154 | 155 | ## Understanding Resource Dependencies 156 | 157 | Notice how Terraform automatically determines the order of resource creation: 158 | 1. Teams must be created before repository permissions can be granted 159 | 2. The repository must exist before branch protection rules can be applied 160 | 3. Branch protection must be in place before the CODEOWNERS file can be added 161 | 162 | This is handled through both implicit dependencies (where Terraform determines relationships based on resource references) and explicit dependencies (using depends_on). 163 | 164 | ## Verification Steps 165 | 166 | In the GitHub web interface: 167 | 1. Navigate to your repository's settings 168 | 2. Verify the teams exist 169 | 3. Check the branch protection rules 170 | 4. Confirm the `.gitignore` file exists and is properly configured 171 | 172 | ## Success Criteria 173 | Your lab is successful if: 174 | - All resources are created successfully 175 | - Resource dependencies are properly maintained 176 | - Teams have the correct permissions 177 | - Branch protection rules are properly configured 178 | - The CODEOWNERS file is created and references the correct team 179 | 180 | ## Additional Exercises 181 | 1. Add more team members to the teams 182 | 2. Create additional branch protection rules for other branches 183 | 3. Add more repository files using Terraform 184 | 4. Modify team permissions and observe the changes 185 | 186 | ## Common Issues and Solutions 187 | 188 | If you see errors: 189 | - Verify GitHub organization permissions 190 | - Ensure team names are unique within your organization 191 | - Check that branch names match exactly 192 | - Verify your GitHub token has sufficient permissions 193 | 194 | ## Next Steps 195 | In the next lab, we will learn about state management. Keep your Terraform configuration files intact, as we will continue to expand upon them. -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AZURE/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-10-AZ: Managing Explicit Dependencies with `depends_on` 2 | 3 | ## Overview 4 | This lab demonstrates how to use Terraform's `depends_on` meta-argument with Azure resources. You'll learn when to use explicit dependencies versus relying on implicit dependencies, using only free Azure resources. 5 | 6 | [![Lab 10](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - Azure free account 13 | - Basic understanding of Terraform and Azure concepts 14 | 15 | Note: Azure credentials are required for this lab. 16 | 17 | ## How to Use This Hands-On Lab 18 | 19 | 1. **Create a Codespace** from this repo (click the button below). 20 | 2. Once the Codespace is running, open the integrated terminal. 21 | 3. Follow the instructions in each **lab** to complete the exercises. 22 | 23 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 24 | 25 | ## Estimated Time 26 | 15 minutes 27 | 28 | ## Existing Configuration Files 29 | 30 | The lab directory contains the following initial files: 31 | 32 | - `variables.tf` 33 | - `providers.tf` 34 | - `main.tf` 35 | 36 | ## Lab Steps 37 | 38 | ### 1. Identify Implicit Dependencies 39 | 40 | Examine the `main.tf` file and identify the **implicit** dependencies: 41 | - Virtual Network depends on Resource Group (via `resource_group_name`) 42 | - Subnet depends on Virtual Network (via `virtual_network_name`) 43 | - NSG depends on Resource Group (via `resource_group_name`) 44 | - Storage Account depends on Resource Group (via `resource_group_name`) 45 | 46 | ### 2. Initialize Terraform 47 | 48 | Initialize your Terraform workspace: 49 | ```bash 50 | terraform init 51 | ``` 52 | 53 | ### 3. Run an Initial Plan and Apply 54 | 55 | Create the initial resources: 56 | ```bash 57 | terraform plan 58 | terraform apply 59 | ``` 60 | 61 | Notice how Terraform automatically determines the correct order based on implicit dependencies: 62 | 1. The resource group is created first 63 | 2. The virtual network is created along with the security group 64 | 3. Then the subnet is created (since it depends on the virtual network and the resource group) 65 | 66 | ### 4. Add NSG to Subnet (Potential Dependency Issue) 67 | 68 | Add the following resources to `main.tf`: 69 | 70 | ```hcl 71 | # Subnet NSG Association 72 | resource "azurerm_subnet_network_security_group_association" "example" { 73 | subnet_id = azurerm_subnet.example.id 74 | network_security_group_id = azurerm_network_security_group.example.id 75 | } 76 | ``` 77 | 78 | ### 5. Add Storage Containers 79 | 80 | Add storage containers that **implicitly** depend on the storage account: 81 | 82 | ```hcl 83 | # Storage Containers 84 | resource "azurerm_storage_container" "logs" { 85 | name = "logs" 86 | storage_account_id = azurerm_storage_account.example.name 87 | container_access_type = "private" 88 | } 89 | 90 | resource "azurerm_storage_container" "data" { 91 | name = "data" 92 | storage_account_id = azurerm_storage_account.example.name 93 | container_access_type = "private" 94 | } 95 | ``` 96 | 97 | ### 6. Add Resources with Explicit Dependencies 98 | 99 | Now, add the following resources that require explicit dependencies: 100 | 101 | ```hcl 102 | # Storage container with explicit dependency 103 | resource "azurerm_storage_container" "backups" { 104 | name = "backups" 105 | storage_account_id = azurerm_storage_account.example.id 106 | container_access_type = "private" 107 | 108 | # Explicitly depend on the other containers - this ensures the other containers are created first 109 | depends_on = [ 110 | azurerm_storage_container.logs, 111 | azurerm_storage_container.data 112 | ] 113 | } 114 | 115 | # Network Security Group Rule with explicit dependency 116 | resource "azurerm_network_security_rule" "https" { 117 | name = "AllowHTTPS" 118 | priority = 101 119 | direction = "Inbound" 120 | access = "Allow" 121 | protocol = "Tcp" 122 | source_port_range = "*" 123 | destination_port_range = "443" 124 | source_address_prefix = "*" 125 | destination_address_prefix = "*" 126 | resource_group_name = azurerm_resource_group.example.name 127 | network_security_group_name = azurerm_network_security_group.example.name 128 | 129 | # Explicitly depend on the NSG association to ensure the NSG is attached to the subnet before adding rules 130 | depends_on = [azurerm_subnet_network_security_group_association.example] 131 | } 132 | ``` 133 | 134 | ### 7. Apply and Observe the Order of Resource Creation 135 | 136 | ```bash 137 | terraform apply 138 | ``` 139 | 140 | Watch how Terraform respects both your **implicit** and **explicit** dependencies. 141 | 142 | ### 8. Add Outputs 143 | 144 | Create an `outputs.tf` file: 145 | 146 | ```hcl 147 | output "resource_group_name" { 148 | description = "Name of the Resource Group" 149 | value = azurerm_resource_group.example.name 150 | } 151 | 152 | output "virtual_network_id" { 153 | description = "ID of the Virtual Network" 154 | value = azurerm_virtual_network.example.id 155 | } 156 | 157 | output "storage_account_name" { 158 | description = "Name of the Storage Account" 159 | value = azurerm_storage_account.example.name 160 | } 161 | 162 | output "storage_containers" { 163 | description = "Names of the Storage Containers" 164 | value = [ 165 | azurerm_storage_container.logs.name, 166 | azurerm_storage_container.data.name, 167 | azurerm_storage_container.backups.name 168 | ] 169 | } 170 | 171 | output "dependency_example" { 172 | description = "Example of dependencies in this lab" 173 | value = { 174 | "Implicit dependencies" = "Resource Group -> VNet -> Subnet, Resource Group -> NSG, Resource Group -> Storage Account" 175 | "Explicit dependencies" = "Logs/Data containers -> Backup container, NSG Association -> NSG Rule" 176 | } 177 | } 178 | ``` 179 | 180 | ### 9. Apply to See Outputs 181 | 182 | ```bash 183 | terraform apply 184 | ``` 185 | 186 | ### 10. Clean Up Resources 187 | 188 | When you're done, clean up all resources: 189 | ```bash 190 | terraform destroy 191 | ``` 192 | 193 | ## Understanding `depends_on` 194 | 195 | ### When to Use `depends_on`: 196 | 1. When there's no implicit dependency (no reference to another resource's attributes) 197 | 2. When a resource needs to be created after another, even though they don't directly reference each other 198 | 3. When you need to ensure a specific creation order for resources 199 | 200 | ### Examples in Azure: 201 | - Storage containers with a specific creation order 202 | - Network security rules that should be created after NSG associations 203 | - Resource associations that depend on both resources being completely provisioned 204 | 205 | ### Syntax: 206 | ```hcl 207 | resource "azurerm_example" "example" { 208 | # ... configuration ... 209 | 210 | depends_on = [ 211 | azurerm_other_resource.name 212 | ] 213 | } 214 | ``` 215 | 216 | ## Additional Exercises 217 | 218 | 1. Add an Azure Private DNS Zone that depends on the VNet 219 | 2. Create multiple storage accounts with dependencies between them 220 | 3. Set up a chain of security rules that depends on each other 221 | 4. Try adding a circular dependency and observe Terraform's error message -------------------------------------------------------------------------------- /labs/lab_06_making_code_dynamic_and_reusable/GITHUB/github.md: -------------------------------------------------------------------------------- 1 | # LAB-06-GH: Refactoring Terraform Configurations: Making Code Dynamic and Reusable 2 | 3 | ## Overview 4 | In this lab, you will examine an existing Terraform configuration with hardcoded values and refactor it to be more dynamic and reusable. You'll implement variables, data sources, and string interpolation to create a more flexible infrastructure definition. The lab uses GitHub free-tier features to ensure no costs are incurred. 5 | 6 | [![Lab 06](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account 13 | - GitHub Personal Access Token (PAT) with appropriate permissions 14 | - Basic understanding of Terraform and GitHub concepts 15 | 16 | Note: GitHub credentials are required for this lab. 17 | 18 | ## How to Use This Hands-On Lab 19 | 20 | 1. **Create a Codespace** from this repo (click the button below). 21 | 2. Once the Codespace is running, open the integrated terminal. 22 | 3. Follow the instructions in each **lab** to complete the exercises. 23 | 24 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 25 | 26 | ## Estimated Time 27 | 45 minutes 28 | 29 | ## Existing Configuration Files 30 | 31 | The lab directory contains the following files with hardcoded values that we'll refactor: 32 | 33 | - `main.tf` 34 | - `providers.tf` 35 | 36 | Examine these files and notice: 37 | - Hardcoded repository name and settings 38 | - Static team name and permissions 39 | - Manual environment naming in topics 40 | - Fixed repository features 41 | - Static team access configuration 42 | 43 | ## Lab Steps 44 | 45 | ### 1. Configure GitHub Credentials 46 | 47 | Set up your GitHub Personal Access Token: 48 | 49 | ```bash 50 | export GITHUB_TOKEN="your_personal_access_token" 51 | ``` 52 | 53 | ### 2. Create Variables File 54 | 55 | Add the following variable declarations to the `variables.tf` file to define variables that will replace hardcoded values: 56 | 57 | ```hcl 58 | variable "environment" { 59 | description = "Environment name for resource naming" 60 | type = string 61 | default = "production" 62 | } 63 | 64 | variable "app_name" { 65 | description = "Application name for repository" 66 | type = string 67 | default = "application" 68 | } 69 | 70 | variable "team_name" { 71 | description = "Base name for the team" 72 | type = string 73 | default = "developers" 74 | } 75 | 76 | variable "repository_features" { 77 | description = "Enabled features for repository" 78 | type = object({ 79 | has_issues = bool 80 | has_wiki = bool 81 | has_discussions = bool 82 | }) 83 | default = { 84 | has_issues = true 85 | has_wiki = true 86 | has_discussions = true 87 | } 88 | } 89 | ``` 90 | 91 | ### 3. Add Data Sources 92 | 93 | Update `main.tf` to include data sources at the top of the file: 94 | 95 | ```hcl 96 | # Get information about the current user 97 | data "github_user" "current" { 98 | username = "" 99 | } 100 | ``` 101 | 102 | ### 4. Refactor Resources 103 | 104 | Replace the existing resources in `main.tf` with this dynamic configuration: 105 | 106 | ```hcl 107 | resource "github_repository" "production" { 108 | name = "${var.environment}-${var.app_name}" # <-- update value here 109 | description = "${title(var.environment)} environment repository managed by ${data.github_user.current.login}" # <-- update value here 110 | visibility = "public" 111 | 112 | has_issues = var.repository_features.has_issues # <-- update value here 113 | has_wiki = var.repository_features.has_wiki # <-- update value here 114 | has_discussions = var.repository_features.has_discussions # <-- update value here 115 | 116 | allow_merge_commit = true 117 | allow_rebase_merge = true 118 | allow_squash_merge = true 119 | 120 | topics = [ 121 | var.environment, # <-- update value here 122 | var.app_name, # <-- update value here 123 | "terraform-managed", 124 | ] 125 | } 126 | ``` 127 | 128 | ### 5. Create Outputs File 129 | 130 | Create `outputs.tf` to display resource information: 131 | 132 | ```hcl 133 | output "repository_url" { 134 | description = "URL of the created repository" 135 | value = github_repository.production.html_url 136 | } 137 | 138 | output "creator_info" { 139 | description = "Information about who created the resources" 140 | value = data.github_user.current.login 141 | } 142 | ``` 143 | 144 | ### 6. Create Environment Configuration 145 | 146 | Create `terraform.tfvars` to define environment-specific values: 147 | 148 | ```hcl 149 | environment = "development" 150 | app_name = "terraform-demo" 151 | repository_features = { 152 | has_issues = true 153 | has_wiki = false 154 | has_discussions = true 155 | } 156 | ``` 157 | 158 | ### 7. Apply and Verify 159 | 160 | Initialize and apply the configuration: 161 | 162 | ```bash 163 | terraform init 164 | terraform plan 165 | terraform apply 166 | ``` 167 | 168 | ### 8. Test Configuration Flexibility 169 | 170 | Create a file called `staging.tfvars`: 171 | 172 | ```hcl 173 | environment = "staging" 174 | app_name = "terraform-demo" 175 | repository_features = { 176 | has_issues = true 177 | has_wiki = true 178 | has_discussions = false 179 | } 180 | ``` 181 | 182 | Apply the new configuration: 183 | ```bash 184 | terraform plan -var-file="staging.tfvars" 185 | terraform apply -var-file="staging.tfvars" 186 | ``` 187 | 188 | Notice how: 189 | - A new repository is created with a different name 190 | - Team names reflect the new environment 191 | - Different repository features are enabled/disabled 192 | - Topics are automatically updated 193 | 194 | ## Understanding the Changes 195 | 196 | Let's examine how the refactoring improves the configuration: 197 | 198 | 1. Variable Usage: 199 | - Repository names are now configurable 200 | - Team names can be changed 201 | - Features can be toggled per environment 202 | - Resources follow consistent naming patterns 203 | 204 | 2. Data Sources: 205 | - User information is dynamically included 206 | - Organization details are automatically added 207 | - Resource descriptions include creator information 208 | 209 | 3. String Interpolation: 210 | - Resource names combine multiple variables 211 | - Descriptions are dynamically generated 212 | - Topics include organization information 213 | 214 | ## Verification Steps 215 | 216 | 1. Check the GitHub web interface to verify: 217 | - Repositories are created with dynamic names 218 | - Teams have correct permissions 219 | - Features are properly configured 220 | - Topics are correctly set 221 | 222 | 2. Test the variable system: 223 | - Modify values in terraform.tfvars 224 | - Observe how changes affect the resources 225 | 226 | ## Clean Up 227 | 228 | Remove all created resources: 229 | 230 | ```bash 231 | terraform destroy 232 | ``` 233 | 234 | ## Additional Exercises 235 | 1. Create additional variable files for different environments 236 | 2. Add more repository features as variables 237 | 3. Implement conditional team creation 238 | 4. Add variable validation rules 239 | 240 | ## Common Issues and Solutions 241 | 242 | If you encounter errors: 243 | - Verify GitHub token permissions 244 | - Ensure organization name is correct 245 | - Check that repository names are unique 246 | - Verify team names don't conflict with existing teams -------------------------------------------------------------------------------- /labs/lab_03_working_with_variables_and_dependencies/azure.md: -------------------------------------------------------------------------------- 1 | # LAB-03-AZ: Working with Variables and Outputs 2 | 3 | ## Overview 4 | In this lab, you will enhance your existing Azure configuration by implementing variables and outputs. You'll learn how variables work, how different variable definitions take precedence, and how to use output values to display resource information. We'll build this incrementally to understand how each change affects our configuration. 5 | 6 | [![Lab 03](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/azure_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - Azure account with appropriate permissions 13 | - Completion of [LAB-02-AZ](https://github.com/btkrausen/terraform-codespaces/blob/main/labs/lab_02_create_your_first_resource/azure.md) with existing Resource Group and VNet configuration 14 | 15 | ## Estimated Time 16 | 20 minutes 17 | 18 | ## Lab Steps 19 | 20 | ### 1. Review Current Configuration 21 | 22 | First, let's review our current main.tf file from the previous lab: 23 | 24 | ```hcl 25 | resource "azurerm_resource_group" "main" { 26 | name = "terraform-course" 27 | location = "eastus" 28 | 29 | tags = { 30 | Name = "terraform-course" 31 | Environment = "learning-terraform" 32 | Managed_By = "Terraform" 33 | } 34 | } 35 | 36 | resource "azurerm_virtual_network" "main" { 37 | name = "terraform-network" 38 | resource_group_name = azurerm_resource_group.main.name 39 | location = azurerm_resource_group.main.location 40 | address_space = ["192.168.0.0/16"] 41 | 42 | tags = { 43 | Name = "terraform-course" 44 | Environment = "learning-terraform" 45 | Managed_By = "Terraform" 46 | } 47 | } 48 | ``` 49 | 50 | ### 2. Add Variable Definitions 51 | 52 | Create or update `variables.tf` with the following content: 53 | 54 | ```hcl 55 | variable "vnet_address_space" { 56 | description = "Address space for Virtual Network" 57 | type = list(string) 58 | default = ["192.168.0.0/16"] 59 | } 60 | 61 | variable "environment" { 62 | description = "Environment name for tagging" 63 | type = string 64 | default = "learning-terraform" 65 | } 66 | 67 | variable "location" { 68 | description = "Azure region for resources" 69 | type = string 70 | default = "eastus" 71 | } 72 | ``` 73 | 74 | Run a plan to see the current state: 75 | ```bash 76 | terraform plan 77 | ``` 78 | 79 | > You should see no changes planned because we haven't implemented the variables yet. 80 | 81 | ### 3. Update Main Configuration to Use Variables 82 | 83 | Now modify `main.tf` to use the new variables: 84 | 85 | ```hcl 86 | resource "azurerm_resource_group" "main" { 87 | name = "terraform-course" 88 | location = var.location # <-- update value here 89 | 90 | tags = { 91 | Name = "terraform-course" 92 | Environment = var.environment # <-- update value here 93 | Managed_By = "Terraform" 94 | } 95 | } 96 | 97 | resource "azurerm_virtual_network" "main" { 98 | name = "terraform-network" 99 | resource_group_name = azurerm_resource_group.main.name 100 | location = azurerm_resource_group.main.location 101 | address_space = var.vnet_address_space # <-- update value here 102 | 103 | tags = { 104 | Name = "terraform-course" 105 | Environment = var.environment # <-- update value here 106 | Managed_By = "Terraform" 107 | } 108 | } 109 | ``` 110 | 111 | Run a plan to see how these variables affect our configuration: 112 | ```bash 113 | terraform plan 114 | ``` 115 | 116 | > You should see no changes planned because our variable values match our current configuration. We just simply moved them from hardcoded values to being declared in our variable definition. 117 | 118 | ### 4. Create terraform.tfvars 119 | 120 | Now let's create `terraform.tfvars`: 121 | 122 | ```bash 123 | touch terraform.tfvars 124 | ``` 125 | You can also just right-click the terraform directory on the left and select **New file** 126 | 127 | Add the following variable values to the `terraform.tfvars` file to override our defaults with new values: 128 | ```hcl 129 | vnet_address_space = ["10.0.0.0/16"] 130 | environment = "development" 131 | ``` 132 | 133 | Run another plan: 134 | ```bash 135 | terraform plan 136 | ``` 137 | 138 | Now you should see that Terraform plans to destroy and recreate the resources because: 139 | - The Virtual Network address space will change from 192.168.0.0/16 to 10.0.0.0/16 140 | - The Environment tag will change from "learning-terraform" to "development" 141 | 142 | Apply the changes: 143 | ```bash 144 | terraform apply 145 | ``` 146 | 147 | Review the proposed changes and type `yes` when prompted to confirm. 148 | 149 | ### 6. Add Output Definitions 150 | 151 | Create a new file named `outputs.tf` and add the following output blocks: 152 | 153 | ```hcl 154 | output "resource_group_id" { 155 | description = "ID of the created Resource Group" 156 | value = azurerm_resource_group.main.id 157 | } 158 | 159 | output "vnet_id" { 160 | description = "ID of the created Virtual Network" 161 | value = azurerm_virtual_network.main.id 162 | } 163 | 164 | output "vnet_address_space" { 165 | description = "Address space of the Virtual Network" 166 | value = azurerm_virtual_network.main.address_space 167 | } 168 | ``` 169 | 170 | Run terraform apply to register the outputs: 171 | ```bash 172 | terraform apply 173 | ``` 174 | 175 | You should now see the output values displayed after the apply completes. 176 | 177 | ### 7. Experiment with Variable Precedence 178 | 179 | Create a new file named `testing.tfvars`: 180 | ```hcl 181 | vnet_address_space = ["172.16.0.0/16"] 182 | environment = "testing" 183 | ``` 184 | 185 | Try running a plan with this new variable file to see how to specify a specific variables file: 186 | ```bash 187 | terraform plan -var-file="testing.tfvars" 188 | ``` 189 | 190 | You'll see that these values would override both the defaults and the values in `terraform.tfvars`. 191 | 192 | ### 8. Delete the Testing File 193 | 194 | Delete the file `testing.tfvars`. 195 | 196 | Run a `terraform plan` to validate that no changes are needed since our real-world infrastructure matches our Terraform configuration. 197 | 198 | ## Verification Steps 199 | 200 | After each step, verify: 201 | 1. The plan output matches expectations 202 | 2. You understand which variable values take precedence 203 | 3. The resource attributes reflect the correct values 204 | 4. The tags are properly applied 205 | 5. The outputs display the correct information 206 | 207 | ## Success Criteria 208 | Your lab is successful if you understand: 209 | - How variable definitions work 210 | - How terraform.tfvars overrides default values 211 | - How provider-level default tags are applied 212 | - How to use output values 213 | - The order of variable precedence in Terraform 214 | 215 | ## Additional Exercises 216 | 1. Try using command-line variables: terraform plan -var="environment=production" 217 | 2. Create additional output values for other resource attributes 218 | 3. Experiment with changing values in different variable files 219 | 220 | ## Common Issues and Solutions 221 | 222 | If you see unexpected changes: 223 | - Review the variable precedence order 224 | - Check which variable files are being used 225 | - Verify the current state of your resources 226 | 227 | ## Next Steps 228 | In the next lab, we will expand our infrastructure by adding multiple resources that depend on each other. Keep your Terraform configuration files intact, as we will continue to expand upon them. -------------------------------------------------------------------------------- /labs/lab_02_create_your_first_resource/aws.md: -------------------------------------------------------------------------------- 1 | # LAB-02-AWS: Creating Your First AWS Resource 2 | 3 | ## Overview 4 | In this lab, you will create your first AWS resource using Terraform: a Virtual Private Cloud (VPC). We will build upon the configuration files created in LAB-01, adding resource configuration and implementing the full Terraform workflow. The lab introduces environment variables for AWS credentials, resource blocks, and the essential Terraform commands for resource management. 5 | 6 | [![Lab 02](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml/badge.svg?branch=main&event=push&job=lab_02)](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - AWS CLI installed 13 | - AWS account with appropriate permissions 14 | - Completion of [LAB-01-AWS](https://github.com/btkrausen/terraform-codespaces/blob/main/labs/lab_01_getting_started_with_terraform/aws.md) 15 | 16 | ## Estimated Time 17 | 20 minutes 18 | 19 | ## Lab Steps 20 | 21 | ### 1. Navigate to Your Configuration Directory 22 | 23 | Ensure you're in the terraform directory created in LAB-01: 24 | 25 | ```bash 26 | pwd 27 | /workspaces/terraform-codespaces/labs/terraform 28 | ``` 29 | If you're in a different directory, change to the Terraform working directory: 30 | ```bash 31 | cd labs/terraform 32 | ``` 33 | 34 | ### 2. Configure AWS Credentials 35 | 36 | Set your AWS credentials as environment variables: 37 | 38 | ```bash 39 | export AWS_ACCESS_KEY_ID="your_access_key" 40 | export AWS_SECRET_ACCESS_KEY="your_secret_key" 41 | ``` 42 | 43 | ### 3. Add VPC Resource Configuration 44 | 45 | Open `main.tf` and add the following VPC configuration (purposely not written in HCL canonical style): 46 | 47 | ```hcl 48 | # Create the primary VPC for workloads 49 | resource "aws_vpc" "main" { 50 | cidr_block = "10.0.0.0/16" 51 | enable_dns_hostnames = true 52 | enable_dns_support = true 53 | 54 | tags = { 55 | Name = "terraform-course" 56 | Environment = "Lab" 57 | Managed_By = "Terraform" 58 | } 59 | } 60 | ``` 61 | 62 | ### 4. Format and Validate 63 | 64 | Format your configuration to rewrite it to follow HCL style: 65 | ```bash 66 | terraform fmt 67 | ``` 68 | 69 | Validate the syntax: 70 | ```bash 71 | terraform validate 72 | ``` 73 | 74 | ### 5. Review the Plan 75 | 76 | Generate and review the execution plan: 77 | ```bash 78 | terraform plan 79 | ``` 80 | 81 | The plan output will show that Terraform intends to create a new VPC with: 82 | - CIDR block of 10.0.0.0/16 83 | - DNS features enabled 84 | - Three tags: Name, Environment, and Managed_By 85 | 86 | ### 6. Apply the Configuration 87 | 88 | Apply the configuration to create the VPC: 89 | ```bash 90 | terraform apply 91 | ``` 92 | 93 | Review the proposed changes and type `yes` when prompted to confirm. 94 | 95 | ### 7. Verify the Resource 96 | 97 | Verify the VPC creation using the AWS CLI: 98 | ```bash 99 | aws ec2 describe-vpcs --filters "Name=tag:Name,Values=terraform-course" --region=us-east-1 # Update your region, if different 100 | ``` 101 | 102 | ### 8. Update the VPC Resource 103 | 104 | In the `main.tf` file, and update the VPC configuration: 105 | 106 | ```hcl 107 | # Create the primary VPC for workloads 108 | resource "aws_vpc" "main" { 109 | cidr_block = "192.168.0.0/16" # <-- change IP Address 110 | enable_dns_hostnames = true 111 | enable_dns_support = true 112 | 113 | tags = { 114 | Name = "terraform-course" 115 | Environment = "Lab" 116 | Managed_By = "Terraform" 117 | } 118 | } 119 | ``` 120 | 121 | ### 9. Run a Terraform Plan to Perform a Dry Run 122 | 123 | Generate and review the execution plan: 124 | ```bash 125 | terraform plan 126 | ``` 127 | 128 | Since the IP address of a VPC cannot be changed, the plan output will show that Terraform intends to replace the VPC: 129 | - the VPC with a CIDR block of `10.0.0.0/16` will be destroyed 130 | - a VPC with a CIDR block of `192.168.0.0/16` will be created 131 | 132 | Expected Output: 133 | 134 | ```bash 135 | aws_vpc.main: Refreshing state... [id=vpc-xxxxx] 136 | 137 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 138 | -/+ destroy and then create replacement 139 | 140 | Terraform will perform the following actions: 141 | 142 | # aws_vpc.main must be replaced 143 | ``` 144 | 145 | ### 10. Apply the Configuration 146 | 147 | Apply the configuration to create the VPC: 148 | ```bash 149 | terraform apply 150 | ``` 151 | 152 | Review the proposed changes and type `yes` when prompted to confirm. 153 | 154 | ### 11. Update the Tags on the VPC 155 | 156 | In the `main.tf` file, and update the VPC configuration: 157 | 158 | ```hcl 159 | # Create the primary VPC for workloads 160 | resource "aws_vpc" "main" { 161 | cidr_block = "192.168.0.0/16" 162 | enable_dns_hostnames = true 163 | enable_dns_support = true 164 | 165 | tags = { 166 | Name = "terraform-course" 167 | Environment = "learning-terraform" # <-- change tag here 168 | Managed_By = "Terraform" 169 | } 170 | } 171 | ``` 172 | 173 | ### 12. Run a Terraform Plan to Perform a Dry Run 174 | 175 | Generate and review the execution plan: 176 | ```bash 177 | terraform plan 178 | ``` 179 | 180 | Since the tags of a VPC can be changed, the plan output will show that Terraform will make an update in-place: 181 | - the tags of the VPC will be updated 182 | 183 | Expected Output: 184 | 185 | ``` 186 | aws_vpc.main: Refreshing state... [id=vpc-xxxxxx] 187 | 188 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 189 | ~ update in-place 190 | 191 | Terraform will perform the following actions: 192 | 193 | # aws_vpc.main will be updated in-place 194 | ``` 195 | 196 | ### 13. Apply the Configuration 197 | 198 | 199 | Apply the configuration to create the VPC: 200 | ```bash 201 | terraform apply 202 | ``` 203 | 204 | Review the proposed changes and type `yes` when prompted to confirm. 205 | 206 | ## Verification Steps 207 | 208 | Confirm that: 209 | 1. The VPC exists in your AWS account with: 210 | - CIDR block: `192.168.0.0/16` 211 | - DNS hostnames enabled 212 | - DNS support enabled 213 | - All specified tags present 214 | 2. A terraform.tfstate file exists in your directory 215 | 3. All Terraform commands completed successfully 216 | 217 | ## Success Criteria 218 | Your lab is successful if: 219 | - AWS credentials are properly configured using environment variables 220 | - The VPC is successfully created with all specified configurations 221 | - All Terraform commands execute without errors 222 | - The terraform.tfstate file accurately reflects your infrastructure 223 | - The resource is successfully destroyed during cleanup 224 | 225 | ## Additional Exercises 226 | 1. Try changing the VPC tags and observe how Terraform handles the modification 227 | 2. Experiment with different CIDR blocks (ensuring they are valid) 228 | 3. Review the terraform.tfstate file to understand how Terraform tracks resource state 229 | 230 | ## Common Issues and Solutions 231 | 232 | If you encounter credential errors: 233 | - Double-check your environment variable values 234 | - Ensure there are no extra spaces or special characters 235 | - Verify your AWS user has appropriate permissions 236 | 237 | If you see CIDR block conflicts: 238 | - Ensure your chosen CIDR block doesn't overlap with existing VPCs 239 | - Verify the CIDR block follows proper formatting (e.g., 10.0.0.0/16) 240 | 241 | ## Next Steps 242 | In the next lab, we will build upon this VPC by adding additional networking components. Keep your Terraform configuration files intact, as we will continue to expand upon them. -------------------------------------------------------------------------------- /labs/lab_10_managing_explicit_dependencies_with_depends_on/AWS/aws.md: -------------------------------------------------------------------------------- 1 | # LAB-10-AWS: Managing Explicit Dependencies with depends_on 2 | 3 | ## Overview 4 | This lab demonstrates how to use Terraform's `depends_on` meta-argument with AWS resources. You'll learn when to use explicit dependencies versus relying on implicit dependencies, using only free AWS resources. 5 | 6 | [![Lab 10](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/aws_lab_validation.yml) 7 | 8 | ## Prerequisites 9 | - Terraform installed 10 | - AWS free tier account 11 | - Basic understanding of Terraform and AWS concepts 12 | 13 | Note: AWS credentials are required for this lab. 14 | 15 | ## How to Use This Hands-On Lab 16 | 17 | 1. **Create a Codespace** from this repo (click the button below). 18 | 2. Once the Codespace is running, open the integrated terminal. 19 | 3. Follow the instructions in each **lab** to complete the exercises. 20 | 21 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/btkrausen/terraform-codespaces) 22 | 23 | ## Estimated Time 24 | 15 minutes 25 | 26 | ## Existing Configuration Files 27 | 28 | The lab directory contains the following initial files that you will use to learn about explicit dependencies: 29 | 30 | - `main.tf` 31 | - `variables.tf` 32 | - `providers.tf` 33 | 34 | ## Lab Steps 35 | 36 | ### 1. Identify Implicit Dependencies 37 | 38 | Examine the `main.tf` file and identify the **implicit** dependencies: 39 | - Subnet depends on VPC (via `vpc_id`) 40 | - Internet Gateway depends on VPC (via `vpc_id`) 41 | - Route Table depends on VPC (via `vpc_id`) and Internet Gateway (via `gateway_id`) 42 | - S3 Bucket implicitly depends on the `random_string` resource 43 | 44 | ### 2. Configure AWS Credentials 45 | 46 | Set up your AWS credentials as environment variables: 47 | 48 | ```bash 49 | export AWS_ACCESS_KEY_ID="your_access_key" 50 | export AWS_SECRET_ACCESS_KEY="your_secret_key" 51 | ``` 52 | 53 | ### 3. Initialize Terraform 54 | 55 | Initialize your Terraform workspace: 56 | ```bash 57 | # Ensure you're in the right directory 58 | cd /workspaces/terraform-codespaces/labs/lab_10_managing_explicit_dependencies_with_depends_on/AWS 59 | 60 | terraform init 61 | ``` 62 | 63 | ### 4. Run an Initial Plan and Apply 64 | 65 | Create the initial resources: 66 | ```bash 67 | terraform plan 68 | terraform apply -auto-approve 69 | ``` 70 | 71 | Notice how Terraform automatically determines the correct order based on implicit dependencies. 72 | 73 | ### 5. Add Resources with Potential Dependency Issues 74 | 75 | Add the following resources to `main.tf`: 76 | 77 | ```hcl 78 | # Route Table Association 79 | resource "aws_route_table_association" "public" { 80 | subnet_id = aws_subnet.public.id 81 | route_table_id = aws_route_table.public.id 82 | } 83 | 84 | # Security Group 85 | resource "aws_security_group" "web" { 86 | name = "web-sg" 87 | description = "Allow web traffic" 88 | vpc_id = aws_vpc.main.id 89 | 90 | egress { 91 | from_port = 0 92 | to_port = 0 93 | protocol = "-1" 94 | cidr_blocks = ["0.0.0.0/0"] 95 | } 96 | 97 | tags = { 98 | Name = "web-sg" 99 | Environment = var.environment 100 | } 101 | } 102 | 103 | # S3 Bucket Policy 104 | resource "aws_s3_bucket_policy" "logs_policy" { 105 | bucket = aws_s3_bucket.logs.id 106 | 107 | policy = jsonencode({ 108 | Version = "2012-10-17" 109 | Statement = [ 110 | { 111 | Action = [ 112 | "s3:GetObject" 113 | ] 114 | Effect = "Allow" 115 | Resource = [ 116 | aws_s3_bucket.logs.arn, 117 | "${aws_s3_bucket.logs.arn}/*" 118 | ] 119 | Principal = { 120 | AWS = "${data.aws_caller_identity.current.arn}" 121 | } 122 | } 123 | ] 124 | }) 125 | } 126 | ``` 127 | 128 | ### 6. Add Resources with Explicit Dependencies 129 | 130 | Now, add the following resources that require **explicit** dependencies on the previously added resources: 131 | 132 | ```hcl 133 | # Security Group Rule with explicit dependency 134 | resource "aws_security_group_rule" "http" { 135 | type = "ingress" 136 | from_port = 80 137 | to_port = 80 138 | protocol = "tcp" 139 | cidr_blocks = ["0.0.0.0/0"] 140 | security_group_id = aws_security_group.web.id 141 | 142 | # Explicitly depend on the route table association to ensure 143 | # network routing is set up before allowing traffic 144 | depends_on = [aws_route_table_association.public] 145 | } 146 | 147 | # S3 Bucket Versioning with explicit dependency 148 | resource "aws_s3_bucket_versioning" "logs_versioning" { 149 | bucket = aws_s3_bucket.logs.id 150 | 151 | versioning_configuration { 152 | status = "Enabled" 153 | } 154 | 155 | # Explicitly depend on the bucket policy 156 | # This ensures the policy is fully applied before enabling versioning 157 | depends_on = [aws_s3_bucket_policy.logs_policy] 158 | } 159 | 160 | # S3 Bucket Logging configuration 161 | resource "aws_s3_bucket_logging" "logs_logging" { 162 | bucket = aws_s3_bucket.logs.id 163 | 164 | target_bucket = aws_s3_bucket.logs.id 165 | target_prefix = "log/" 166 | 167 | # Explicitly depend on bucket versioning 168 | # This creates a chain of dependencies: policy -> versioning -> logging 169 | depends_on = [aws_s3_bucket_versioning.logs_versioning] 170 | } 171 | ``` 172 | 173 | ### 7. Apply and Observe Order 174 | ```bash 175 | terraform apply 176 | ``` 177 | 178 | Watch how Terraform respects both your implicit and explicit dependencies. 179 | 180 | ### 8. Add Outputs 181 | 182 | Create an outputs.tf file: 183 | 184 | ```hcl 185 | output "vpc_id" { 186 | description = "ID of the VPC" 187 | value = aws_vpc.main.id 188 | } 189 | 190 | output "subnet_id" { 191 | description = "ID of the public subnet" 192 | value = aws_subnet.public.id 193 | } 194 | 195 | output "s3_bucket_name" { 196 | description = "Name of the S3 bucket for logs" 197 | value = aws_s3_bucket.logs.bucket 198 | } 199 | 200 | output "security_group_id" { 201 | description = "ID of the security group" 202 | value = aws_security_group.web.id 203 | } 204 | 205 | output "dependency_example" { 206 | description = "Example of dependencies in this lab" 207 | value = { 208 | "Implicit dependencies" = "VPC -> Subnet, VPC -> IGW, IGW -> Route Table" 209 | "Explicit dependencies" = "Route Table Association -> SG Rule, Bucket Policy -> Versioning -> Logging Bucket" 210 | } 211 | } 212 | ``` 213 | 214 | ### 9. Apply to See Outputs 215 | ```bash 216 | terraform apply 217 | ``` 218 | 219 | ### 10. Clean Up Resources 220 | 221 | When you're done, clean up all resources: 222 | ```bash 223 | terraform destroy 224 | ``` 225 | 226 | ## Understanding depends_on 227 | 228 | ### When to Use depends_on: 229 | 1. When there's no implicit dependency (no reference to another resource's attributes) 230 | 2. When a resource needs to be created after another, even though they don't directly reference each other 231 | 3. When you need to ensure a specific creation order for resources 232 | 233 | ### Examples in AWS: 234 | - S3 bucket configurations that should be applied in a specific order 235 | - Security group rules that depend on network routing being established 236 | - IAM policies that reference resources by ARN but need those resources to be fully created first 237 | 238 | ### Syntax: 239 | ```hcl 240 | resource "aws_example" "example" { 241 | # ... configuration ... 242 | 243 | depends_on = [ 244 | aws_other_resource.name 245 | ] 246 | } 247 | ``` 248 | 249 | ## Additional Exercises 250 | 251 | 1. Add an S3 bucket lifecycle policy that depends on the bucket versioning 252 | 2. Create a chain of three related security group rules that depend on each other 253 | 3. Try adding a circular dependency and observe Terraform's error message -------------------------------------------------------------------------------- /labs/lab_05_working_with_state_data_sources_and_cli/github.md: -------------------------------------------------------------------------------- 1 | # LAB-05-GH: State Management, Data Sources, and Advanced CLI 2 | 3 | ## Overview 4 | In this lab, you will learn how to work with Terraform state, use data sources to query GitHub information, and explore additional Terraform CLI commands. You'll create a production environment configuration, learn how to inspect and manage state, and properly clean up all resources. 5 | 6 | [![Lab 05](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account with appropriate permissions 13 | - Completion of [LAB-04-GH](https://github.com/btkrausen/terraform-codespaces/blob/main/labs/lab_04_managing_mulitple_resources/github.md) with existing repository configuration 14 | 15 | ## Estimated Time 16 | 30 minutes 17 | 18 | ## Lab Steps 19 | 20 | ### 1. Understanding Terraform State 21 | 22 | Examine your current state with the following commands: 23 | 24 | ```bash 25 | terraform state list 26 | terraform state show github_repository.example 27 | ``` 28 | 29 | This displays all resources in your state and details about a specific resource. 30 | 31 | ### 2. Create Data Sources for GitHub Information 32 | 33 | Add the top of `main.tf` with the following content: 34 | 35 | ```hcl 36 | # Get information about the current GitHub user 37 | data "github_user" "current" { 38 | username = "" 39 | } 40 | 41 | # Get information about the example repository 42 | data "github_repository" "existing_example" { 43 | full_name = "terraform_user/${github_repository.example.name}" 44 | depends_on = [github_repository.example] 45 | } 46 | ``` 47 | 48 | ### 3. Add Production Repository Variables 49 | 50 | Add the following to your `variables.tf` file: 51 | 52 | ```hcl 53 | # Production Repository Variables 54 | variable "prod_repository_name" { 55 | description = "Name of the Production GitHub repository" 56 | type = string 57 | default = "production-repo" 58 | } 59 | 60 | variable "prod_branch_protection" { 61 | description = "Number of required approvals for production branch" 62 | type = number 63 | default = 2 64 | } 65 | ``` 66 | 67 | ### 4. Update terraform.tfvars 68 | 69 | Add the production values to your existing `terraform.tfvars`: 70 | 71 | ```hcl 72 | # Production Repo Configurations 73 | prod_repository_name = "production-repo" 74 | prod_branch_protection = 3 75 | ``` 76 | 77 | ### 5. Create Production Repository Resources 78 | 79 | Add the following to your `main.tf` file: 80 | 81 | ```hcl 82 | # Create production repository 83 | resource "github_repository" "production" { 84 | name = var.prod_repository_name 85 | description = "Production repository managed by Terraform" 86 | visibility = "public" 87 | 88 | auto_init = true 89 | 90 | has_issues = true 91 | has_discussions = false 92 | has_wiki = false 93 | 94 | vulnerability_alerts = true 95 | 96 | topics = ["terraform", "production"] 97 | } 98 | 99 | # Create stricter branch protection for production 100 | resource "github_branch_protection" "production" { 101 | repository_id = github_repository.production.node_id 102 | pattern = "main" 103 | 104 | enforce_admins = true 105 | 106 | required_pull_request_reviews { 107 | required_approving_review_count = var.prod_branch_protection 108 | dismiss_stale_reviews = true 109 | require_code_owner_reviews = true 110 | } 111 | 112 | require_signed_commits = true 113 | } 114 | 115 | # Create a production environment 116 | resource "github_repository_environment" "production" { 117 | repository = github_repository.production.name 118 | environment = "production" 119 | 120 | reviewers { 121 | users = [data.github_user.current.id] 122 | } 123 | 124 | deployment_branch_policy { 125 | protected_branches = true 126 | custom_branch_policies = false 127 | } 128 | } 129 | ``` 130 | 131 | ### 6. Add Repository Files Using Data Sources 132 | 133 | Add the following to `main.tf`: 134 | 135 | ```hcl 136 | # Create a README file that references all repositories 137 | resource "github_repository_file" "production_readme" { 138 | repository = github_repository.production.name 139 | branch = "main" 140 | file = "README.md" 141 | content = <<-EOT 142 | # Production Repository 143 | 144 | This repository is managed by Terraform. 145 | 146 | ## Related Repositories 147 | 148 | - Development: [${github_repository.development.name}](${github_repository.development.html_url}) 149 | - Example: [${github_repository.example.name}](${github_repository.example.html_url}) 150 | EOT 151 | commit_message = "Add README via Terraform" 152 | commit_author = "Terraform User" 153 | commit_email = "terraform@course.com" 154 | overwrite_on_create = true 155 | } 156 | ``` 157 | 158 | ### 7. Add Outputs for Data Sources 159 | 160 | Add the following to your `outputs.tf` file: 161 | 162 | ```hcl 163 | # Data source outputs 164 | output "current_user" { 165 | description = "Current GitHub user name" 166 | value = data.github_user.current.name 167 | } 168 | 169 | # Production repository outputs 170 | output "production_repo_url" { 171 | description = "URL of the production repository" 172 | value = github_repository.production.html_url 173 | } 174 | 175 | output "production_environment" { 176 | description = "Production environment name" 177 | value = github_repository_environment.production.environment 178 | } 179 | ``` 180 | 181 | ### 8. Apply the Configuration and Explore State 182 | 183 | Run the following commands: 184 | 185 | ```bash 186 | terraform fmt 187 | terraform validate 188 | terraform plan 189 | terraform apply 190 | ``` 191 | 192 | ### 9. Advanced State Commands 193 | 194 | Try these state management commands: 195 | 196 | ```bash 197 | # List all resources in the state 198 | terraform state list 199 | 200 | # Show details of a specific resource 201 | terraform state show github_repository.production 202 | 203 | # Create a state backup 204 | terraform state pull > terraform.tfstate.backup 205 | 206 | # Perform a targeted apply 207 | terraform apply -target=github_repository_file.production_readme 208 | ``` 209 | 210 | ### 10. Clean Up Resources 211 | 212 | To remove specific resources: 213 | 214 | ```bash 215 | # Only destroy the repository file 216 | terraform destroy -target=github_repository_file.production_readme 217 | ``` 218 | 219 | For complete cleanup after completing Labs 1-5: 220 | 221 | ```bash 222 | terraform destroy 223 | ``` 224 | 225 | ## Verification Steps 226 | 227 | In the GitHub web interface: 228 | 1. Verify the production repository was created 229 | 2. Check the README file contains references to other repositories 230 | 3. Examine the production environment settings 231 | 232 | ## Success Criteria 233 | Your lab is successful if: 234 | - All resources are created successfully 235 | - Data sources correctly retrieve GitHub information 236 | - Production environment is properly configured 237 | - State commands work as expected 238 | 239 | ## Additional Exercises 240 | 1. Import an existing repository using `terraform import` 241 | 2. Create a workspace for different environments 242 | 3. Use the `terraform output` command to extract specific values 243 | 244 | ## Common Issues and Solutions 245 | 246 | - Ensure your GitHub token has sufficient permissions 247 | - Check if referenced resources exist before using them in data sources 248 | - If hitting API rate limits, space out your commands 249 | 250 | ## Next Steps 251 | In the next part of your Terraform journey, consider exploring remote state backends, modules, and integrating Terraform with CI/CD pipelines. -------------------------------------------------------------------------------- /labs/lab_02_create_your_first_resource/github.md: -------------------------------------------------------------------------------- 1 | # LAB-02-GH: Creating Your First GitHub Resource 2 | 3 | ## Overview 4 | In this lab, you will create your first GitHub resources using Terraform: a repository and branch protection rules. We will build upon the configuration files created in LAB-01, adding resource configuration and implementing the full Terraform workflow. The lab introduces environment variables for GitHub authentication, resource blocks, and the essential Terraform commands for resource management. 5 | 6 | [![Lab 02](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml/badge.svg?branch=main)](https://github.com/btkrausen/terraform-testing/actions/workflows/github_lab_validation.yml) 7 | 8 | **Preview Mode**: Use `Cmd/Ctrl + Shift + V` in VSCode to see a nicely formatted version of this lab! 9 | 10 | ## Prerequisites 11 | - Terraform installed 12 | - GitHub account with appropriate permissions 13 | - GitHub Personal Access Token (with repo and admin:org permissions) 14 | - Completion of [LAB-01-GH](https://github.com/btkrausen/terraform-codespaces/blob/main/labs/lab_01_getting_started_with_terraform/github.md) 15 | 16 | ## Estimated Time 17 | 20 minutes 18 | 19 | ## Lab Steps 20 | 21 | ### 1. Navigate to Your Configuration Directory 22 | 23 | Ensure you're in the terraform directory created in LAB-01: 24 | 25 | ```bash 26 | pwd 27 | /workspaces/terraform-codespaces/labs/terraform 28 | ``` 29 | If you're in a different directory, change to the Terraform working directory: 30 | ```bash 31 | cd labs/terraform 32 | ``` 33 | 34 | ### 2. Configure GitHub Credentials 35 | 36 | Set your GitHub credentials as environment variables: 37 | 38 | ```bash 39 | export GITHUB_TOKEN="your_personal_access_token" 40 | ``` 41 | 42 | ### 3. Add Resource Configuration 43 | 44 | Open `main.tf` and add the following configuration (purposely not written in HCL canonical style): 45 | 46 | ```hcl 47 | # Create the repository 48 | resource "github_repository" "example" { 49 | name = "terraform-example" 50 | description = "Repository created by Terraform" 51 | visibility = "public" 52 | 53 | auto_init = true 54 | 55 | has_issues = true 56 | has_discussions = true 57 | has_wiki = true 58 | 59 | allow_merge_commit = true 60 | allow_squash_merge = true 61 | allow_rebase_merge = true 62 | 63 | topics = ["terraform", "infrastructure-as-code"] 64 | } 65 | 66 | # Create branch protection rule 67 | resource "github_branch_protection" "main" { 68 | repository_id = github_repository.example.node_id 69 | pattern = "main" 70 | 71 | required_pull_request_reviews { 72 | required_approving_review_count = 1 73 | } 74 | } 75 | ``` 76 | 77 | ### 4. Format and Validate 78 | 79 | Format your configuration to rewrite it to follow HCL style: 80 | ```bash 81 | terraform fmt 82 | ``` 83 | 84 | Validate the syntax: 85 | ```bash 86 | terraform validate 87 | ``` 88 | 89 | ### 5. Review the Plan 90 | 91 | Generate and review the execution plan: 92 | ```bash 93 | terraform plan 94 | ``` 95 | 96 | The plan output will show that Terraform intends to create: 97 | - A new private repository with specified features 98 | - A branch protection rule requiring one review for the main branch 99 | 100 | ### 6. Apply the Configuration 101 | 102 | Apply the configuration to create the resources: 103 | ```bash 104 | terraform apply 105 | ``` 106 | 107 | Review the proposed changes and type `yes` when prompted to confirm. 108 | 109 | ### 7. Verify the Resources 110 | 111 | Let's verify our resources in the GitHub web interface: 112 | 113 | 1. Open your web browser and navigate to `GitHub.com` 114 | 2. Go to your repositories list 115 | 3. You should see the new `terraform-example` repository 116 | 4. Click into the repository to verify: 117 | - The repository description 118 | - The enabled features (Issues, Discussions, Wiki) 119 | - The repository topics 120 | 5. Navigate to Settings → Branches to verify: 121 | - The branch protection rule is applied to the main branch 122 | - Pull request reviews are required 123 | 124 | ### 8. Update the Repository Settings 125 | 126 | In the `main.tf` file, update the repository configuration: 127 | 128 | ```hcl 129 | resource "github_repository" "terraform" { 130 | name = "terraform-course-repo" 131 | description = "Updated repository description" # <-- change description 132 | visibility = "public" 133 | 134 | auto_init = true 135 | 136 | has_issues = true 137 | has_discussions = true 138 | has_wiki = false # <-- change wiki setting 139 | 140 | allow_merge_commit = true 141 | allow_squash_merge = true 142 | allow_rebase_merge = true 143 | 144 | topics = ["terraform", "infrastructure-as-code", "learning"] # <-- add topic 145 | } 146 | ``` 147 | 148 | ### 9. Run a Terraform Plan to Perform a Dry Run 149 | 150 | Generate and review the execution plan: 151 | ```bash 152 | terraform plan 153 | ``` 154 | 155 | The plan output will show that Terraform will update the repository in-place: 156 | - The description will be updated 157 | - Wiki feature will be disabled 158 | - A new topic will be added 159 | 160 | ### 10. Apply the Configuration 161 | 162 | Apply the configuration to update the repository: 163 | ```bash 164 | terraform apply 165 | ``` 166 | 167 | Review the proposed changes and type `yes` when prompted to confirm. 168 | 169 | ### 11. Update the Branch Protection 170 | 171 | In the `main.tf` file, update the branch protection configuration: 172 | 173 | ```hcl 174 | resource "github_branch_protection" "main" { 175 | repository_id = github_repository.terraform.node_id 176 | pattern = "main" 177 | 178 | required_pull_request_reviews { 179 | required_approving_review_count = 2 # <-- increase required reviewers 180 | } 181 | } 182 | ``` 183 | 184 | ### 12. Run a Terraform Plan to Perform a Dry Run 185 | 186 | Generate and review the execution plan: 187 | ```bash 188 | terraform plan 189 | ``` 190 | 191 | The plan output will show that Terraform will update the branch protection rule: 192 | - Required reviewers will be increased to `2` 193 | 194 | ### 13. Apply the Configuration 195 | 196 | Apply the configuration to update the branch protection: 197 | ```bash 198 | terraform apply 199 | ``` 200 | 201 | Review the proposed changes and type `yes` when prompted to confirm. 202 | 203 | ## Verification Steps 204 | 205 | Confirm that: 206 | 1. The resources exist in your GitHub account with: 207 | - Repository named `terraform-course-repo` 208 | - Updated description and topics 209 | - Wiki disabled 210 | - Branch protection requiring `2` reviewers 211 | 2. A `terraform.tfstate` file exists in your directory 212 | 3. All Terraform commands completed successfully 213 | 214 | ## Success Criteria 215 | Your lab is successful if: 216 | - GitHub credentials are properly configured using environment variables 217 | - The resources are successfully created with all specified configurations 218 | - All Terraform commands execute without errors 219 | - The `terraform.tfstate` file accurately reflects your infrastructure 220 | - The resources are successfully destroyed during cleanup 221 | 222 | ## Additional Exercises 223 | 1. Try changing other repository settings (e.g., merge options) 224 | 2. Add additional branch protection rules 225 | 3. Review the `terraform.tfstate` file to understand how Terraform tracks resource state 226 | 227 | ## Common Issues and Solutions 228 | 229 | If you encounter credential errors: 230 | - Double-check your personal access token 231 | - Ensure the token has the required permissions 232 | - Verify the token hasn't expired 233 | 234 | If you see permission issues: 235 | - Verify you have admin access to the repository 236 | - Check that your token includes required scopes 237 | - Ensure you're not hitting repository limits for your account type 238 | 239 | ## Next Steps 240 | In the next lab, we will build upon this by adding repository collaborators and additional settings. Keep your Terraform configuration files intact, as we will continue to expand upon them. --------------------------------------------------------------------------------