├── .gitignore ├── 001-output-module-values ├── .terraform.lock.hcl ├── README.md ├── main.tf └── modules │ └── network │ └── network.tf ├── 002-set-remote-backend ├── .terraform.lock.hcl ├── README.md └── provider.tf ├── 003-variable-local-output ├── .terraform.lock.hcl ├── README.md └── main.tf ├── 004-variable-types ├── README.md └── main.tf ├── 005-destroy-resources-methods ├── .terraform.lock.hcl ├── README.md ├── main.tf └── provider.tf ├── 006-terraform-for-each-usage ├── .terraform.lock.hcl ├── README.md ├── main.tf ├── provider.tf └── variables.tf ├── 007-terraform-workspace-example ├── .DS_Store ├── .terraform.lock.hcl ├── README.md ├── main.tf └── provider.tf ├── 008-terraform-provider-aliases ├── README.md ├── main.tf └── provider.tf ├── 009-terraform-version-constraints ├── README.md └── providers.tf ├── 010-resources-import ├── README.md └── main.tf ├── 011-terraform-dynamic-blocks ├── README.md ├── main.tf ├── provider.tf └── variables.tf └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | -------------------------------------------------------------------------------- /001-output-module-values/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.107.0" 6 | hashes = [ 7 | "h1:gk6yMuxWOxN01e68uTwJvQ91x8roEwyeYJBwNrYFHIk=", 8 | "zh:0a5bfcdef1dad509c4f45c0ada2c8e2cc058cf9542ddec48fbee18c4097bce9e", 9 | "zh:0b56736691e4b28ea15b381a4711ff39719ff83a40ce97cd283eb21988f471f6", 10 | "zh:13d55030c8be466b5de4819e4a8b84da69a40b15bfa0cc2588f5270b4682fa89", 11 | "zh:1eac398718cd0973f94015e49ff69a6ed8c860d69e4adbd192c7bea775af2941", 12 | "zh:7b1984b60abc7f53298950680bda504eca8d70c9d0d906d6dee2aac6a827f9d6", 13 | "zh:86f63ad98576d698c6ba8defa9165160633f086145a1f060014a93f5c2fb384e", 14 | "zh:afc78e7e0e76b4d2593ca2ec78b064c896888d03c6cb82f2c5bd37e815e056e7", 15 | "zh:b84997b287c673b297ede08404133279dbc72f070c8d6e4284bf62637de4bfb4", 16 | "zh:dd1d21c8a37938082a5c2497eacd76bacb1ac459bc9d38ee782443fa87a2247d", 17 | "zh:edcaca84c6473427d36f940748e5ce4d1d50b393012f6f6c0ec4303792f607d9", 18 | "zh:f0892ecd0eea0c06710056048d8bb75c4c3bda74de7ba41afa60d7b9c9a3b0ca", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /001-output-module-values/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Output module values 3 | 4 | ![](https://cdn-images-1.medium.com/max/3840/1*abWDBlVoYeUhLQ5J1zBI4A.jpeg) 5 | 6 | There are several reasons why you might want to output [**module**](https://developer.hashicorp.com/terraform/language/modules) values: 7 | 8 | 1. **Inter-module dependencies**: If you have multiple modules and one depends on the value from another, you can use output values to expose that information. For example, if a networking module creates a virtual network and a compute module needs the ID of that virtual network to create instances, the networking module can output the virtual network ID and the compute module can reference it. 9 | 10 | 2. **Inspection**: Output values can be used to print certain values in the command line after a terraform apply operation. This can be useful for debugging, or for getting information about the infrastructure that was created, such as virtual network IDs, public IP addresses, etc. 11 | 12 | 3. **Consumption by external programs**: If you’re using Terraform in automation, the calling process can use output values to get information about what was created. This can be useful if the calling process needs to use this information to influence subsequent steps. 13 | 14 | ![](https://cdn-images-1.medium.com/max/2000/1*eiWHXUCDj5whZKLtNCyPsg.png) 15 | 16 | Here’s how you can output that value in your root module: 17 | 18 | First, define the child module and call it in your root module. 19 | 20 | module "network" { 21 | source = "./modules/network" // path to the child module 22 | 23 | // Pass variables to the module 24 | resource_group_name = azurerm_resource_group.this.name 25 | location = azurerm_resource_group.this.location 26 | address_space = "10.0.0.0/16" 27 | } 28 | 29 | In your child module (e.g: located at ./modules/network), you might have something like this: 30 | 31 | output "vnet_id" { 32 | value = azurerm_virtual_network.vnet.id 33 | } 34 | 35 | Then, in your root module, you can output the vnet_id like this: 36 | 37 | output "vnet_id" { 38 | value = module.network.vnet_id 39 | description = "The ID of the virtual network" 40 | } 41 | 42 | In this example, vnet_id is the name of the output, module.network.vnet_id is the value of the output, and description is an optional field that describes what the output is. 43 | 44 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101/tree/main/001-output-module-values). I hope you find it useful! 45 | -------------------------------------------------------------------------------- /001-output-module-values/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | resource "azurerm_resource_group" "this" { 6 | name = "my-resource-group" 7 | location = "West Europe" 8 | } 9 | 10 | module "network" { 11 | source = "./modules/network" // path to the child module 12 | 13 | // Pass variables to the module 14 | resource_group_name = azurerm_resource_group.this.name 15 | location = azurerm_resource_group.this.location 16 | address_space = "10.0.0.0/16" 17 | } 18 | 19 | output "vnet_id" { 20 | value = module.network.vnet_id 21 | description = "The ID of the virtual network" 22 | } -------------------------------------------------------------------------------- /001-output-module-values/modules/network/network.tf: -------------------------------------------------------------------------------- 1 | variable "resource_group_name" {} 2 | variable "location" {} 3 | variable "address_space" {} 4 | 5 | resource "azurerm_virtual_network" "vnet" { 6 | name = "myVNet" 7 | resource_group_name = var.resource_group_name 8 | location = var.location 9 | address_space = [var.address_space] 10 | } 11 | 12 | output "vnet_id" { 13 | value = azurerm_virtual_network.vnet.id 14 | } -------------------------------------------------------------------------------- /002-set-remote-backend/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.107.0" 6 | hashes = [ 7 | "h1:gk6yMuxWOxN01e68uTwJvQ91x8roEwyeYJBwNrYFHIk=", 8 | "zh:0a5bfcdef1dad509c4f45c0ada2c8e2cc058cf9542ddec48fbee18c4097bce9e", 9 | "zh:0b56736691e4b28ea15b381a4711ff39719ff83a40ce97cd283eb21988f471f6", 10 | "zh:13d55030c8be466b5de4819e4a8b84da69a40b15bfa0cc2588f5270b4682fa89", 11 | "zh:1eac398718cd0973f94015e49ff69a6ed8c860d69e4adbd192c7bea775af2941", 12 | "zh:7b1984b60abc7f53298950680bda504eca8d70c9d0d906d6dee2aac6a827f9d6", 13 | "zh:86f63ad98576d698c6ba8defa9165160633f086145a1f060014a93f5c2fb384e", 14 | "zh:afc78e7e0e76b4d2593ca2ec78b064c896888d03c6cb82f2c5bd37e815e056e7", 15 | "zh:b84997b287c673b297ede08404133279dbc72f070c8d6e4284bf62637de4bfb4", 16 | "zh:dd1d21c8a37938082a5c2497eacd76bacb1ac459bc9d38ee782443fa87a2247d", 17 | "zh:edcaca84c6473427d36f940748e5ce4d1d50b393012f6f6c0ec4303792f607d9", 18 | "zh:f0892ecd0eea0c06710056048d8bb75c4c3bda74de7ba41afa60d7b9c9a3b0ca", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /002-set-remote-backend/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Set remote backend 3 | 4 | ![](https://cdn-images-1.medium.com/max/3840/1*_6hSYe-P2QjjlMHTO6HQAw.jpeg) 5 | 6 | In Terraform, the term “[**backend**](https://developer.hashicorp.com/terraform/language/settings/backends/configuration)” refers to the system used to store the Terraform state file. The state file is a JSON file that Terraform generates to track the resources it manages. By default, Terraform stores this file on your local filesystem. However, you can configure Terraform to store the state file remotely, which is what the backend configuration is for. 7 | 8 | Storing Terraform state remotely, especially in a shared storage like Azure Storage, has several advantages: 9 | 10 | 1. **Collaboration**: When working in a team, storing state remotely allows multiple people to work on the same Terraform configuration without overwriting each other’s changes. 11 | 12 | 2. **Security**: State files can contain sensitive information. Storing state remotely allows you to take advantage of the security features of the remote storage, such as encryption at rest. 13 | 14 | 3. **Versioning**: Some remote storage solutions provide versioning. This can be useful for auditing changes and rolling back to a previous state if necessary. 15 | 16 | 4. **Locking**: Remote state allows Terraform to lock the state file when it’s being modified, preventing concurrent runs that could lead to conflicts or inconsistencies. 17 | 18 | 5. **Continuity**: If your local machine encounters an issue, the state file stored remotely remains unaffected. This is particularly important in production environments. 19 | 20 | Let’s set up Azure as the provider and configure Azure storage as the backend for storing Terraform state. Here is a breakdown of what each part does: 21 | 22 | terraform { 23 | backend "azurerm" { 24 | resource_group_name = "" 25 | storage_account_name = "" 26 | container_name = "" 27 | key = "" 28 | use_azuread_auth = true 29 | } 30 | } 31 | 32 | provider "azurerm" { 33 | features {} 34 | } 35 | 36 | * terraform block: This block is used to configure certain Terraform behaviors, including the backend for storing state. 37 | 38 | * backend "azurerm" block: This block configures Azure Storage as the backend for storing Terraform state. The resource_group_name, storage_account_name, container_name, and key fields should be filled in with the details of your Azure Storage account. The use_azuread_auth = true line indicates that Azure AD authentication is being used to access the storage account. So you should set up the Azure Storage in advance and have the required permission, like Storage Blob Data Contributor. 39 | 40 | ![](https://cdn-images-1.medium.com/max/3624/1*3Yepv7fSbRRXL499qCfinw.png) 41 | 42 | * provider "azurerm" block: This block configures the Azure provider. The features {} block is required for the Azure provider but doesn't need to contain anything. 43 | 44 | You can then set these values when you run the terraform init command with the -backend-config flag, as follows: 45 | 46 | terraform init -backend-config="resource_group_name=rg" -backend-config="storage_account_name=st" -backend-config="container_name=tfstate" -backend-config="key=dev.terraform.tfstate" 47 | 48 | ![](https://cdn-images-1.medium.com/max/3500/1*osoR5pDt8NKWtGGmUf-s_A.png) 49 | 50 | 51 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101/tree/main/002-set-remote-backend). I hope you find it useful! 52 | -------------------------------------------------------------------------------- /002-set-remote-backend/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "azurerm" { 3 | resource_group_name = "" 4 | storage_account_name = "" 5 | container_name = "" 6 | key = "" 7 | use_azuread_auth = true 8 | } 9 | } 10 | 11 | provider "azurerm" { 12 | features {} 13 | } -------------------------------------------------------------------------------- /003-variable-local-output/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.107.0" 6 | hashes = [ 7 | "h1:gk6yMuxWOxN01e68uTwJvQ91x8roEwyeYJBwNrYFHIk=", 8 | "zh:0a5bfcdef1dad509c4f45c0ada2c8e2cc058cf9542ddec48fbee18c4097bce9e", 9 | "zh:0b56736691e4b28ea15b381a4711ff39719ff83a40ce97cd283eb21988f471f6", 10 | "zh:13d55030c8be466b5de4819e4a8b84da69a40b15bfa0cc2588f5270b4682fa89", 11 | "zh:1eac398718cd0973f94015e49ff69a6ed8c860d69e4adbd192c7bea775af2941", 12 | "zh:7b1984b60abc7f53298950680bda504eca8d70c9d0d906d6dee2aac6a827f9d6", 13 | "zh:86f63ad98576d698c6ba8defa9165160633f086145a1f060014a93f5c2fb384e", 14 | "zh:afc78e7e0e76b4d2593ca2ec78b064c896888d03c6cb82f2c5bd37e815e056e7", 15 | "zh:b84997b287c673b297ede08404133279dbc72f070c8d6e4284bf62637de4bfb4", 16 | "zh:dd1d21c8a37938082a5c2497eacd76bacb1ac459bc9d38ee782443fa87a2247d", 17 | "zh:edcaca84c6473427d36f940748e5ce4d1d50b393012f6f6c0ec4303792f607d9", 18 | "zh:f0892ecd0eea0c06710056048d8bb75c4c3bda74de7ba41afa60d7b9c9a3b0ca", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /003-variable-local-output/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: variable, local, output 3 | 4 | ![](https://cdn-images-1.medium.com/max/3840/1*KnjCOwpDuH9ykcOJi9tSPA.jpeg) 5 | 6 | In Terraform, variable, local, and output are used for different purposes: 7 | 8 | * variable: This is an **input** to your Terraform configuration. It allows you to define values that can be reused throughout your configuration and can be overridden when running Terraform commands. For example, you might define a variable for the region where you want to create resources. 9 | 10 | * local: This is a way to define **a named expression** that allows you to simplify complex expressions and avoid repeating the same values. Locals are similar to variables, but they're defined within the module and can't be set from outside the module. 11 | 12 | * output: This is a way to **display information** about what your configuration created. For example, you might output the IP address of a server that your configuration created. 13 | 14 | Here’s an example that demonstrates the difference: 15 | 16 | provider "azurerm" { 17 | features{} 18 | } 19 | 20 | variable "region" { 21 | description = "The region where resources should be created" 22 | type = string 23 | default = "australiaeast" 24 | } 25 | 26 | locals { 27 | resource_name = "rg-${var.region}" 28 | } 29 | 30 | resource "azurerm_resource_group" "example" { 31 | name = local.resource_name 32 | location = var.region 33 | } 34 | 35 | output "instance_id" { 36 | description = "The ID of the created instance" 37 | value = azurerm_resource_group.example.id 38 | } 39 | 40 | output "local_value" { 41 | value = local.resource_name 42 | } 43 | 44 | In this example: 45 | 46 | * variable "region" is an input that sets the region where the resource group should be created. It has a default value of "australiaeast", but this can be overridden when running Terraform commands. 47 | 48 | * locals { resource_name = "rg-${var.region}" } is a local value that generates the name of the resource group by concatenating "rg-" with the value of the region variable. 49 | 50 | * output "instance_id" and output "local_value" are outputs that display the ID of the created resource group and the locally defined resource name, respectively. These values can be useful for debugging, for use in other modules, or for consumption by external programs. 51 | 52 | ![](https://cdn-images-1.medium.com/max/2000/1*RaMCix7EoU10e4JB503aVA.png) 53 | 54 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 55 | -------------------------------------------------------------------------------- /003-variable-local-output/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features{} 3 | } 4 | 5 | variable "region" { 6 | description = "The region where resources should be created" 7 | type = string 8 | default = "australiaeast" 9 | } 10 | 11 | locals { 12 | resource_name = "rg-${var.region}" 13 | } 14 | 15 | resource "azurerm_resource_group" "example" { 16 | name = local.resource_name 17 | location = var.region 18 | } 19 | 20 | output "instance_id" { 21 | description = "The ID of the created instance" 22 | value = azurerm_resource_group.example.id 23 | } 24 | 25 | output "local_value" { 26 | value = local.resource_name 27 | } -------------------------------------------------------------------------------- /004-variable-types/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: types of variables 3 | 4 | ![](https://cdn-images-1.medium.com/max/3840/1*Zdvdgz0FLU-5gU8edw2ikw.jpeg) 5 | 6 | In Terraform, the type of variable you use depends on the kind of data you need to represent. Here’s a guide on when to use each type: 7 | 8 | **Primitive Types**: These are the basic data types in Terraform. 9 | 10 | * string: Use this when you need to represent text. For example, you might use a string to represent a resource name, a region, or an API key. 11 | 12 | * number: Use this when you need to represent a numeric value. For example, you might use a number to represent the amount of resources to create. 13 | 14 | * bool: Use this when you need to represent a true or false value. For example, you might use a boolean to control whether certain resources are created. 15 | 16 | **Collection Types**: These are data types that can hold multiple values. 17 | 18 | * list: Use this when you need an ordered collection of items of the same type. For example, you might use a list to represent the availability zones for your resources. 19 | 20 | * set: Use this when you need an unordered collection of unique items of the same type. For example, you might use a set to represent the unique tags for your resources. 21 | 22 | * map: Use this when you need a collection of key-value pairs. For example, you might use a map to represent the tags for your resources, where each tag is a key-value pair. 23 | 24 | **Complex Types**: These are data types that can hold multiple values of different types. 25 | 26 | * object({name = string, age = number}): Use this when you need a collection of named attributes with specific types. For example, you might use an object to represent a complex configuration for a resource. 27 | 28 | Here’s an example that demonstrates the variable types: 29 | 30 | # Primitive types: string, number, bool 31 | variable "string_var" { 32 | description = "This is a string variable" 33 | type = string 34 | default = "Hello, World!" 35 | } 36 | 37 | variable "number_var" { 38 | description = "This is a number variable" 39 | type = number 40 | default = 42 41 | } 42 | 43 | variable "bool_var" { 44 | description = "This is a boolean variable" 45 | type = bool 46 | default = false 47 | } 48 | 49 | # Collection types: list/tuple, map/object, set 50 | variable "list_var" { 51 | description = "This is a list variable" 52 | type = list(string) 53 | default = ["apple", "banana", "cherry", "cherry"] 54 | } 55 | 56 | variable "set_var" { 57 | description = "This is a set variable" 58 | type = set(string) 59 | default = ["apple", "banana", "banana", "banana"] 60 | } 61 | 62 | variable "map_var" { 63 | description = "This is a map variable" 64 | type = map(string) 65 | default = { 66 | key1 = "value1" 67 | key2 = "value2" 68 | } 69 | } 70 | 71 | variable "object_var" { 72 | description = "This is an object variable" 73 | type = object({ 74 | name = string 75 | age = number 76 | }) 77 | default = { 78 | name = "ABC" 79 | age = 123 80 | } 81 | } 82 | 83 | 84 | # Output variables 85 | output "string_output" { 86 | value = var.string_var 87 | } 88 | 89 | output "number_output" { 90 | value = var.number_var 91 | } 92 | 93 | output "bool_output" { 94 | value = var.bool_var 95 | } 96 | 97 | output "list_output" { 98 | value = var.list_var 99 | } 100 | 101 | output "map_output" { 102 | value = var.map_var 103 | } 104 | 105 | output "object_output" { 106 | value = var.object_var 107 | } 108 | 109 | output "set_output" { 110 | value = var.set_var 111 | } 112 | 113 | Each variable is declared with a variable block that includes a description, a type, and a default value. The type can be any of the built-in Terraform types: string, number, bool, list, set, map, object. The default value is optional and provides a default value for the variable if one is not provided when the configuration is applied. 114 | 115 | The variable "set_var" is a set of strings. Even though "banana" is repeated in the default value, it will only appear once in the actual set. 116 | 117 | ![](https://cdn-images-1.medium.com/max/2284/1*NHkxhv7CPWO2TLrag8bAhg.png) 118 | 119 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 120 | -------------------------------------------------------------------------------- /004-variable-types/main.tf: -------------------------------------------------------------------------------- 1 | # Primitive types: string, number, bool 2 | variable "string_var" { 3 | description = "This is a string variable" 4 | type = string 5 | default = "Hello, World!" 6 | } 7 | 8 | variable "number_var" { 9 | description = "This is a number variable" 10 | type = number 11 | default = 42 12 | } 13 | 14 | variable "bool_var" { 15 | description = "This is a boolean variable" 16 | type = bool 17 | default = false 18 | } 19 | 20 | # Collection types: list/tuple, map/object, set 21 | variable "list_var" { 22 | description = "This is a list variable" 23 | type = list(string) 24 | default = ["apple", "banana", "cherry", "cherry"] 25 | } 26 | 27 | variable "set_var" { 28 | description = "A set variable" 29 | type = set(string) 30 | default = ["apple", "banana", "banana", "banana"] 31 | } 32 | 33 | variable "map_var" { 34 | description = "This is a map variable" 35 | type = map(string) 36 | default = { 37 | key1 = "value1" 38 | key2 = "value2" 39 | } 40 | } 41 | 42 | variable "object_var" { 43 | description = "This is an object variable" 44 | type = object({ 45 | name = string 46 | age = number 47 | }) 48 | default = { 49 | name = "ABC" 50 | age = 123 51 | } 52 | } 53 | 54 | 55 | # Output variables 56 | output "string_output" { 57 | value = var.string_var 58 | } 59 | 60 | output "number_output" { 61 | value = var.number_var 62 | } 63 | 64 | output "bool_output" { 65 | value = var.bool_var 66 | } 67 | 68 | output "list_output" { 69 | value = var.list_var 70 | } 71 | 72 | output "map_output" { 73 | value = var.map_var 74 | } 75 | 76 | output "object_output" { 77 | value = var.object_var 78 | } 79 | 80 | output "set_output" { 81 | value = var.set_var 82 | } -------------------------------------------------------------------------------- /005-destroy-resources-methods/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.108.0" 6 | constraints = ">= 3.0.0" 7 | hashes = [ 8 | "h1:pNUojXaVobFjmYvp9HViTZ9FKp6yFy+coPbn4/GR138=", 9 | "zh:2afecf948fd702bc08c87d9114595809d011f99a70a12dbf6bc67a12d0bee5fc", 10 | "zh:395b6d1384a579867064e62d49b0b91e15919c33b03ea8b5031c2779bfa16b3d", 11 | "zh:3e5594c59b6b02bc6e0f4c3de71aa2ab992494c53725fb3c64d36745f3814ef3", 12 | "zh:4613e190609377309f6a4ac44f631c9469efab3ae148dbb09e73718201dc4f42", 13 | "zh:624f01cb7604d58100068401bd07ab09a141e7bd318f8214127838cf202e4868", 14 | "zh:65709950c9933e38704e2075a2339951e1259a6e882f35d390be36e1844ebc72", 15 | "zh:af82657fad4e3a177f2ebb8035b45bda40f8856eb999288533321028794d03e5", 16 | "zh:c40b331eba08830d16c0e6795fa7cbf08231073df2cfdb0f34e9d908a915981a", 17 | "zh:d6ccd533a0bd984ca7ed1ae860e057e9e2f88468745be9712236d2d240353de4", 18 | "zh:f361fd398e8772f8554a010331d161d6f7284a43238fd28bfa7b41795a5538b8", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | "zh:f8c2132c77d35930203ec66f1bf9bbf633a2406e9f7b572ff425d65b8aa8c492", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /005-destroy-resources-methods/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: destroy resources methods 3 | 4 | ![](https://cdn-images-1.medium.com/max/4416/1*UiD0FKxV0Zxj4dkqfMsY1Q.png) 5 | 6 | To destroy resources managed by Terraform, you can use the terraform destroy command. This command will destroy all resources that are defined in your Terraform configuration files and that have been created by Terraform. 7 | 8 | ![](https://cdn-images-1.medium.com/max/3108/1*4gNf-NwIn97oMt5NEt6kgg.png) 9 | > This command is a convenience alias for the terraform apply -destroy command. 10 | 11 | Besides using the command to remove all resources, you can also employ alternative methods to either destroy specific resources or avoid using this command entirely. Let’s explore these options! 12 | 13 | ### **Targeted Destruction with terraform destroy** 14 | 15 | If you want to destroy a specific resource instead of all resources, you can use the -target flag with the terraform destroy command. For example, to destroy the azurerm_storage_account resource from your Terraform file, you would use: 16 | 17 | terraform destroy -target=azurerm_storage_account.this 18 | 19 | This command will only destroy the specified resource and any resources that depend on it. 20 | 21 | ![](https://cdn-images-1.medium.com/max/2408/1*mbz81aftKuP3QBxt98Vh5w.png) 22 | 23 | ### Remove the resource definition in the Terraform configuration. 24 | 25 | If you remove a resource from your Terraform configuration and then run terraform apply, Terraform will delete the resource. 26 | 27 | ![](https://cdn-images-1.medium.com/max/3376/1*16a3BvX2DSSo8YT1RVgkPQ.png) 28 | 29 | When you run terraform apply, Terraform compares your configuration with the current state of your infrastructure. If a resource exists in the state but not in the configuration, Terraform interprets this as you wanting to delete the resource, and it will plan to destroy it. 30 | 31 | ![](https://cdn-images-1.medium.com/max/2212/1*0DakgNQYS9Xk1wZFbZ7ImA.png) 32 | 33 | After you confirm the plan, Terraform will delete the resource. This is one way to delete resources managed by Terraform, but be careful to ensure you are removing the correct resource from your configuration. 34 | 35 | Here’s the example used here: 36 | 37 | terraform { 38 | required_providers { 39 | azurerm = ">= 3.0" 40 | } 41 | } 42 | 43 | provider "azurerm" { 44 | features {} 45 | } 46 | 47 | resource "azurerm_resource_group" "this" { 48 | name = "rg-aue-example-001" 49 | location = "australiaeast" 50 | } 51 | 52 | resource "azurerm_storage_account" "this" { 53 | name = "staaueexample001" 54 | resource_group_name = azurerm_resource_group.this.name 55 | location = azurerm_resource_group.this.location 56 | account_tier = "Standard" 57 | account_replication_type = "LRS" 58 | } 59 | 60 | terraform destroy 61 | terraform destroy -target=azurerm_storage_account.this 62 | 63 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 64 | -------------------------------------------------------------------------------- /005-destroy-resources-methods/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "this" { 2 | name = "rg-aue-example-001" 3 | location = "australiaeast" 4 | } 5 | 6 | resource "azurerm_storage_account" "this" { 7 | name = "staaueexample001" 8 | resource_group_name = azurerm_resource_group.this.name 9 | location = azurerm_resource_group.this.location 10 | account_tier = "Standard" 11 | account_replication_type = "LRS" 12 | } -------------------------------------------------------------------------------- /005-destroy-resources-methods/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = ">= 3.0" 4 | } 5 | } 6 | 7 | provider "azurerm" { 8 | features {} 9 | } -------------------------------------------------------------------------------- /006-terraform-for-each-usage/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.108.0" 6 | constraints = ">= 3.0.0" 7 | hashes = [ 8 | "h1:pNUojXaVobFjmYvp9HViTZ9FKp6yFy+coPbn4/GR138=", 9 | "zh:2afecf948fd702bc08c87d9114595809d011f99a70a12dbf6bc67a12d0bee5fc", 10 | "zh:395b6d1384a579867064e62d49b0b91e15919c33b03ea8b5031c2779bfa16b3d", 11 | "zh:3e5594c59b6b02bc6e0f4c3de71aa2ab992494c53725fb3c64d36745f3814ef3", 12 | "zh:4613e190609377309f6a4ac44f631c9469efab3ae148dbb09e73718201dc4f42", 13 | "zh:624f01cb7604d58100068401bd07ab09a141e7bd318f8214127838cf202e4868", 14 | "zh:65709950c9933e38704e2075a2339951e1259a6e882f35d390be36e1844ebc72", 15 | "zh:af82657fad4e3a177f2ebb8035b45bda40f8856eb999288533321028794d03e5", 16 | "zh:c40b331eba08830d16c0e6795fa7cbf08231073df2cfdb0f34e9d908a915981a", 17 | "zh:d6ccd533a0bd984ca7ed1ae860e057e9e2f88468745be9712236d2d240353de4", 18 | "zh:f361fd398e8772f8554a010331d161d6f7284a43238fd28bfa7b41795a5538b8", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | "zh:f8c2132c77d35930203ec66f1bf9bbf633a2406e9f7b572ff425d65b8aa8c492", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /006-terraform-for-each-usage/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: for_each usage 3 | 4 | ![](https://cdn-images-1.medium.com/max/3840/1*mBSifseORUb7l5xsOvUNrg.jpeg) 5 | 6 | The for_each argument in Terraform allows you to create multiple instances of a resource based on a **map** or a **set** of strings. It creates a direct association between a resource instance and an element in your set or map. 7 | 8 | Here’s how you can use for_each to create multiple instances of a resource in different Azure regions: 9 | 10 | variable "regions" { 11 | description = "A set of regions" 12 | type = set(string) 13 | default = ["australiaeast", "australiasoutheast", "australiacentral"] 14 | } 15 | 16 | locals { 17 | naming = { 18 | australiaeast = "aue" 19 | australiasoutheast = "aus" 20 | australiacentral = "auc" 21 | } 22 | } 23 | 24 | resource "azurerm_resource_group" "this" { 25 | for_each = var.regions 26 | name = "rg-${local.naming[each.key]}-example-001" 27 | location = each.key 28 | } 29 | 30 | resource "azurerm_storage_account" "this" { 31 | for_each = azurerm_resource_group.this 32 | name = "sta${local.naming[each.value.location]}example001" 33 | resource_group_name = each.value.name 34 | location = each.value.location 35 | account_tier = "Standard" 36 | account_replication_type = "LRS" 37 | } 38 | > To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). 39 | 40 | In the azurerm_resource_group block: 41 | 42 | resource "azurerm_resource_group" "this" { 43 | for_each = var.regions 44 | name = "rg-${local.naming[each.key]}-example-001" 45 | location = each.key 46 | } 47 | 48 | for_each is set to var.regions, which should be a map or set of regions. Terraform will create an instance of azurerm_resource_group for each element in var.regions. The each.key and each.value are used to access the key and value of each element in the map or set. 49 | 50 | In the next block: 51 | 52 | resource "azurerm_storage_account" "this" { 53 | for_each = azurerm_resource_group.this 54 | name = "sta${local.naming[each.value.location]}example001" 55 | resource_group_name = each.value.name 56 | location = each.value.location 57 | account_tier = "Standard" 58 | account_replication_type = "LRS" 59 | } 60 | 61 | 62 | for_each is set to azurerm_resource_group.this, which is a map of the resource groups created in the previous step. Terraform will create an instance of azurerm_storage_account for each resource group. The each.value **object** contains the attributes of each resource group, which are used to set the resource_group_name and location arguments for each storage account. 63 | 64 | Run terraform plan followed by terraform apply to deploy the resources to Azure. The following screenshot demonstrates the successful creation of three storage account instances, each located in a different region. 65 | 66 | ![](https://cdn-images-1.medium.com/max/6760/1*vUQTotBvyt52IV-RohHv2w.png) 67 | 68 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 69 | -------------------------------------------------------------------------------- /006-terraform-for-each-usage/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | naming = { 3 | australiaeast = "aue" 4 | australiasoutheast = "aus" 5 | australiacentral = "auc" 6 | } 7 | } 8 | 9 | resource "azurerm_resource_group" "this" { 10 | for_each = var.regions 11 | name = "rg-${local.naming[each.key]}-example-001" 12 | location = each.key 13 | } 14 | 15 | resource "azurerm_storage_account" "this" { 16 | for_each = azurerm_resource_group.this 17 | name = "sta${local.naming[each.value.location]}example001" 18 | resource_group_name = each.value.name 19 | location = each.value.location 20 | account_tier = "Standard" 21 | account_replication_type = "LRS" 22 | } -------------------------------------------------------------------------------- /006-terraform-for-each-usage/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = ">= 3.0" 6 | } 7 | } 8 | } 9 | 10 | provider "azurerm" { 11 | features {} 12 | } -------------------------------------------------------------------------------- /006-terraform-for-each-usage/variables.tf: -------------------------------------------------------------------------------- 1 | variable "regions" { 2 | description = "A set of regions" 3 | type = set(string) 4 | default = ["australiaeast", "australiasoutheast", "australiacentral"] 5 | } -------------------------------------------------------------------------------- /007-terraform-workspace-example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjd/terraform-101/53b4813d94756e142923c9793639144e10364b82/007-terraform-workspace-example/.DS_Store -------------------------------------------------------------------------------- /007-terraform-workspace-example/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azurerm" { 5 | version = "3.109.0" 6 | constraints = ">= 3.0.0" 7 | hashes = [ 8 | "h1:tb3a5x6HV4YRxyL3VpdTWe1vsKocKi1HT0KFWnF5ZjM=", 9 | "zh:4324c3df26709c7e669b751259cc5e62c4694ab44370dfcdfe197dcd9261c365", 10 | "zh:4e3e83649240cea7105cd2802d0ae64b143fb543c2f559173feae5a108bc4287", 11 | "zh:74ebf6be1277e9bd357b011026b80fc5ec1c26b70ec7ddd5fcae5e977f9a66ef", 12 | "zh:82cfd3c92035f834a05f4b91d813a059a29ff4157792e36a0b3a224cba8737ae", 13 | "zh:93f05c8ae3555c885c84b82781b2e90774671c321138b7f3c38ecd498009e1d8", 14 | "zh:9b445a9a1544b4b38db10fadbd9ffd5efdded0def54feb9ca593e1bec6fbec5f", 15 | "zh:b21ccd2c1bc691cf2f9876482b6e226d8a37a48de951b168a10f96ba929ebefd", 16 | "zh:b7b7e458eb3c22669e1d36e9ef1886272c10f310501001abce8ae76383014fa5", 17 | "zh:bd3c0cf7caab0a989227934bc60a8ac27131efcf84dd77cb6e32e68374170aee", 18 | "zh:f4b9ccbb28eadf3825f6d7d38a3519379de222f136235a2f21a96c0221d65fb8", 19 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 20 | "zh:f8ef0b4a970ff5edeadfdeed77f9d0682befdca5df4e9b6d9dcfdf9903305b26", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /007-terraform-workspace-example/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Terraform Workspace Example 3 | 4 | ![To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101).](https://cdn-images-1.medium.com/max/3840/1*ZDo03QD0jk8rgjX9IvAVWQ.jpeg) 5 | 6 | Terraform workspaces allow you to manage different states of your infrastructure parallelly. This feature is useful for managing multiple environments (e.g., development, staging, production) without needing to duplicate your Terraform configuration files. 7 | 8 | Here’s a step-by-step guide on how to use Terraform workspaces: 9 | 10 | ### Step 1: Initialize Your Terraform Configuration 11 | 12 | Before you can use workspaces, you need to initialize your Terraform configuration directory with terraform init. 13 | 14 | terraform init 15 | 16 | ### Step 2: Create a New Workspace 17 | 18 | After initialization, Terraform starts with a **default workspace** named default. 19 | 20 | ![](https://cdn-images-1.medium.com/max/2196/1*-qwITD7fBm-0PyM3wRf74w.png) 21 | 22 | To create a new workspace, use terraform workspace new followed by the name of the workspace. 23 | 24 | terraform workspace new dev 25 | 26 | This command creates and switches to a workspace named dev. 27 | 28 | ![](https://cdn-images-1.medium.com/max/3016/1*6tcGoipZKnlLnVNrDphpyA.png) 29 | 30 | ### Step 3: Switch Between Workspaces 31 | 32 | To switch between existing workspaces, use terraform workspace select followed by the name of the workspace. 33 | 34 | terraform workspace select default 35 | 36 | ![](https://cdn-images-1.medium.com/max/2688/1*A1gEpc5SI2YXA_CfTszi6Q.png) 37 | 38 | ### Step 4: Use Workspaces in Your Configuration 39 | 40 | You can use the ${terraform.workspace} interpolation within your Terraform configuration to differentiate resources across workspaces. For example: 41 | 42 | resource "azurerm_resource_group" "this" { 43 | name = "rg-aue-example-${terraform.workspace}-001" 44 | location = "australiaeast" 45 | } 46 | 47 | In this example, resources will have the workspace name included in their names, allowing you to easily identify which workspace a resource belongs to. 48 | 49 | ### Step 5: Plan and Apply 50 | 51 | When you run terraform plan or terraform apply, Terraform will use the configuration for the current workspace. Each workspace has its own state file, so changes in one workspace won't affect resources in another. 52 | 53 | terraform plan 54 | terraform apply 55 | 56 | ![](https://cdn-images-1.medium.com/max/2184/1*S3KKk2MZrjYsmhOL03pcCw.png) 57 | 58 | ### Step 6: List Workspaces 59 | 60 | To see all your workspaces, use terraform workspace list. 61 | 62 | terraform workspace list 63 | 64 | ![](https://cdn-images-1.medium.com/max/2172/1*cltGFrNc7QAVGHuf_pPKTA.png) 65 | 66 | ### Step 7: Delete a Workspace 67 | 68 | If you need to delete a workspace, first switch to a different workspace (**you cannot delete the current workspace**). Then use terraform workspace delete followed by the name of the workspace. 69 | 70 | terraform workspace select default 71 | terraform workspace delete dev 72 | 73 | Remember, **deleting a workspace does not destroy the resources managed by that workspace**. If you want to destroy the resources, you should run terraform destroy while the workspace is selected, before deleting the workspace. 74 | 75 | Workspaces are a powerful feature for managing multiple sets of infrastructure with the same codebase. 76 | 77 | However, for complex scenarios or large teams, it might be more manageable to use separate configuration directories or modules, leveraging version control branches for each environment. 78 | 79 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 80 | -------------------------------------------------------------------------------- /007-terraform-workspace-example/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "this" { 2 | name = "rg-aue-example-${terraform.workspace}-001" 3 | location = "australiaeast" 4 | } -------------------------------------------------------------------------------- /007-terraform-workspace-example/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = ">= 3.0" 6 | } 7 | } 8 | } 9 | 10 | provider "azurerm" { 11 | features {} 12 | } -------------------------------------------------------------------------------- /008-terraform-provider-aliases/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Terraform Provider Aliases 3 | 4 | ![To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101).](https://cdn-images-1.medium.com/max/3840/1*6R1ulnqyS8XPibgOiiHUfA.jpeg) 5 | 6 | To create multiple instances of a provider in Terraform, especially for Azure, you can define multiple provider blocks with different **aliases** and configurations. This approach is useful when you need to manage resources ***across different Azure subscriptions, regions, or both***. 7 | 8 | Suppose you have Azure subscriptions for development and production environments. You can define providers with aliases for each environment and then use these aliases to create resources in the respective environments. 9 | 10 | Here’s an example of how to configure multiple Azure provider instances with aliases: 11 | 12 | # Define the provider for the primary Azure subscription 13 | provider "azurerm" { 14 | features {} 15 | # Primary subscription details 16 | subscription_id = "primary-subscription-id" 17 | alias = "primary" 18 | } 19 | 20 | # Define the provider for the secondary Azure subscription 21 | provider "azurerm" { 22 | features {} 23 | # Secondary subscription details 24 | subscription_id = "secondary-subscription-id" 25 | alias = "secondary" 26 | } 27 | 28 | # Use the primary provider to create a resource group in the primary subscription 29 | resource "azurerm_resource_group" "primary_rg" { 30 | provider = azurerm.primary 31 | name = "primary-resource-group" 32 | location = "australiaeast" 33 | } 34 | 35 | # Use the secondary provider to create a resource group in the secondary subscription 36 | resource "azurerm_resource_group" "secondary_rg" { 37 | provider = azurerm.secondary 38 | name = "secondary-resource-group" 39 | location = "australiasoutheast" 40 | } 41 | 42 | In this example, two azurerm provider blocks are defined, each with a different alias (primary and secondary). Each provider block specifies a different subscription_id. When defining resources, you can specify which provider to use by setting the provider attribute to either azurerm.primary or azurerm.secondary. 43 | 44 | ![](https://cdn-images-1.medium.com/max/3592/1*HZJSG09r7MdQkK68KUC5bg.png) 45 | 46 | This allows you to manage resources across different subscriptions within the same Terraform configuration. 47 | 48 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 49 | 50 | 51 | -------------------------------------------------------------------------------- /008-terraform-provider-aliases/main.tf: -------------------------------------------------------------------------------- 1 | # Use the primary provider to create a resource group in the primary subscription 2 | resource "azurerm_resource_group" "primary_rg" { 3 | provider = azurerm.primary 4 | name = "primary-resource-group" 5 | location = "australiaeast" 6 | } 7 | 8 | # Use the secondary provider to create a resource group in the secondary subscription 9 | resource "azurerm_resource_group" "secondary_rg" { 10 | provider = azurerm.secondary 11 | name = "secondary-resource-group" 12 | location = "australiasoutheast" 13 | } -------------------------------------------------------------------------------- /008-terraform-provider-aliases/provider.tf: -------------------------------------------------------------------------------- 1 | # Define the provider for the primary Azure subscription 2 | provider "azurerm" { 3 | features {} 4 | # Primary subscription details 5 | subscription_id = "primary-subscription-id" 6 | alias = "primary" 7 | } 8 | 9 | # Define the provider for the secondary Azure subscription 10 | provider "azurerm" { 11 | features {} 12 | # Secondary subscription details 13 | subscription_id = "secondary-subscription-id" 14 | alias = "secondary" 15 | } 16 | -------------------------------------------------------------------------------- /009-terraform-version-constraints/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Terraform Version Constraints 3 | 4 | ![To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101).](https://cdn-images-1.medium.com/max/3840/1*HhBteDN25YTUuCVi3JKxjg.jpeg) 5 | 6 | In Terraform, you can restrict a provider’s version using the required_providers block within a terraform block in your configuration. There are several ways to specify version constraints, allowing you to precisely control which versions of a provider Terraform can use. Here are the main options: 7 | 8 | 1. **Exact Version**: Specifies an exact version of a provider. 9 | 10 | 2. **Version Range**: Allows any version within a specified range. 11 | 12 | 3. **Minimum Version**: Specifies a minimum version, allowing any version greater than or equal to the specified version. 13 | 14 | 4. **Maximum Version**: Specifies a maximum version, allowing any version less than the specified version. 15 | 16 | 5. **Patch-level changes**: The ~> operator is used in Terraform to specify a version constraint that allows patch-level changes only. 17 | 18 | Here’s an example that demonstrates all these options for the azurerm provider: 19 | 20 | terraform { 21 | required_providers { 22 | azurerm = { 23 | source = "hashicorp/azurerm" 24 | # Exact Version 25 | version = "=3.110.0" 26 | } 27 | aws = { 28 | source = "hashicorp/aws" 29 | # Version Range 30 | version = ">= 3.0.0, < 4.0.0" 31 | } 32 | google = { 33 | source = "hashicorp/google" 34 | # Minimum Version 35 | version = ">= 3.5.0" 36 | } 37 | kubernetes = { 38 | source = "hashicorp/kubernetes" 39 | # Maximum Version 40 | version = "< 2.0.0" 41 | } 42 | random = { 43 | source = "hashicorp/random" 44 | # Patch-level changes within the 2.2 minor version 45 | version = "~> 2.2.0" 46 | } 47 | } 48 | } 49 | 50 | In this example: 51 | 52 | * For azurerm, only version 3.110.0 is allowed. 53 | 54 | * For aws, any version 3.x.x is allowed. 55 | 56 | * For google, version 3.5.0 and any newer version are allowed. 57 | 58 | * For kubernetes, any version before 2.0.0 is allowed. 59 | 60 | * For random, versions 2.2.0, 2.2.1, 2.2.2, etc., but not 2.3.0 or higher. 61 | 62 | These constraints ensure that Terraform uses only provider versions that are compatible with your configuration, helping to avoid unexpected changes or incompatibilities. 63 | 64 | Then run the terraform init command to install the providers that meet the version requirements, as you can see in the following screenshot. 65 | 66 | ![](https://cdn-images-1.medium.com/max/2868/1*gt5UljEAtUsEkT6EsC5eSw.png) 67 | 68 | ### **.terraform.lock.hcl** 69 | 70 | At the same time, you will notice a file called ***.terraform.lock.hcl*** has been created. 71 | 72 | ![](https://cdn-images-1.medium.com/max/2064/1*P6t3sJJ5f9sVVA9pX71CdA.png) 73 | 74 | The .terraform.lock.hcl file is a dependency lock file generated and used by Terraform to record the exact versions of providers and modules that were selected during the last run of terraform init. 75 | 76 | This ensures consistent operation and reproducibility across different environments and Terraform executions by locking the dependencies to specific versions. 77 | 78 | ### terraform init -upgrade 79 | 80 | The terraform init -upgrade command is used to reinitialize your Terraform working directory, with the added effect of upgrading all the modules and providers to the latest versions allowed within the constraints specified in your Terraform configuration files, even if you previously initialized with an older version. 81 | 82 | This command is particularly useful for ensuring that your infrastructure is managed using the latest features, improvements, and bug fixes from your providers and modules, while still respecting the version constraints set to avoid unexpected changes. 83 | 84 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /009-terraform-version-constraints/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | # Exact Version 6 | version = "=3.110.0" 7 | } 8 | aws = { 9 | source = "hashicorp/aws" 10 | # Version Range 11 | version = ">= 3.0.0, < 4.0.0" 12 | } 13 | google = { 14 | source = "hashicorp/google" 15 | # Minimum Version 16 | version = ">= 3.5.0" 17 | } 18 | kubernetes = { 19 | source = "hashicorp/kubernetes" 20 | # Maximum Version 21 | version = "< 2.0.0" 22 | } 23 | random = { 24 | source = "hashicorp/random" 25 | # Patch-level changes within the 2.2 minor version 26 | version = "~> 2.2.0" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /010-resources-import/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Resources Import 3 | 4 | ![To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101).](https://cdn-images-1.medium.com/max/3840/1*uEn1c_Xv7FYVOuqRyLqigg.jpeg) 5 | 6 | The terraform import command is used to import existing infrastructure into your Terraform state. This allows Terraform to manage, update, and delete resources that were created outside of Terraform or by another Terraform configuration. The command is particularly useful for bringing resources under Terraform's management without having to recreate them. 7 | 8 | Let’s go through an example of importing an Azure Storage Account into Terraform management. 9 | 10 | ![](https://cdn-images-1.medium.com/max/2984/1*EjsUV442jxX8Fp5rWZ-5jw.png) 11 | 12 | ### Step 1: Add the Resource to Your Terraform Configuration 13 | 14 | First, define the resource in your Terraform configuration if it’s not already there. 15 | 16 | resource "azurerm_storage_account" "this" { 17 | name = "sttfimportdemo" 18 | resource_group_name = "rg-aue-dev" 19 | location = "australiaeast" 20 | account_tier = "Standard" 21 | account_replication_type = "LRS" 22 | } 23 | 24 | ### Step 2: Find the Resource ID 25 | 26 | For Azure resources, the resource ID is a unique identifier that Azure uses to manage the resource. You can find this ID using the Azure CLI, Azure PowerShell, or through the Azure Portal. 27 | 28 | ![](https://cdn-images-1.medium.com/max/2736/1*XxmSuCG4W4D42WF3hSyYAA.png) 29 | 30 | "id": "/subscriptions/{subscriptionId}/resourceGroups/rg-aue-dev/providers/Microsoft.Storage/storageAccounts/sttfimportdemo", 31 | 32 | ### Step 3: Run the Import Command 33 | 34 | With the resource defined in your Terraform configuration and the resource ID in hand, you can now import the resource. The command format for importing an Azure Storage Account is: 35 | 36 | terraform import azurerm_storage_account.this /subscriptions/{subscriptionId}/resourceGroups/rg-aue-dev/providers/Microsoft.Storage/storageAccounts/sttfimportdemo 37 | 38 | ![](https://cdn-images-1.medium.com/max/2820/1*UbE8zezfAA38tkVrJZXKPg.png) 39 | 40 | ### Step 4: Review and Apply Configuration 41 | 42 | After importing, it’s important to review your Terraform configuration and ensure it accurately reflects the settings of the imported Azure Storage Account. You may need to adjust your configuration to match the actual properties of the resource. 43 | 44 | Run terraform plan to see if there are any differences between your configuration and the real-world resource. If there are differences, Terraform will propose changes to align your configuration with the state of the imported resource. Apply any necessary changes with terraform apply. 45 | 46 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /010-resources-import/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | resource "azurerm_storage_account" "this" { 6 | name = "sttfimportdemo" 7 | resource_group_name = "rg-aue-dev" 8 | location = "australiaeast" 9 | account_tier = "Standard" 10 | account_replication_type = "LRS" 11 | } -------------------------------------------------------------------------------- /011-terraform-dynamic-blocks/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Terraform 101: Dynamic Blocks 3 | 4 | ![To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101)](https://cdn-images-1.medium.com/max/3840/1*oSb1zAEFnpWAflY-Espxww.jpeg) 5 | 6 | Dynamic blocks in Terraform allow you to dynamically construct **repeatable** nested blocks within a resource or data configuration. This feature is particularly useful **when you want to create multiple instances of a block based on the contents of a list or map**, like firewall rules or NSG rules, without having to hard-code each block instance. 7 | 8 | Here’s an example of how to use a dynamic block within an Azure resource configuration. In this example, we’ll dynamically create **network security group (NSG) rules** within an Azure Network Security Group resource based on a list of rule definitions. 9 | 10 | ### Step 1: Define Variables for NSG Rules 11 | 12 | First, define a variable to hold the details of each NSG rule you want to create. This could be a list of maps, where each map contains the properties of a rule. 13 | 14 | variable "nsg_rules" { 15 | description = "List of NSG rules" 16 | type = list(object({ 17 | name = string 18 | priority = number 19 | direction = string 20 | access = string 21 | protocol = string 22 | source_port_range = string 23 | destination_port_range = string 24 | source_address_prefix = string 25 | destination_address_prefix = string 26 | })) 27 | default = [ 28 | { 29 | name = "allow-ssh" 30 | priority = 100 31 | direction = "Inbound" 32 | access = "Allow" 33 | protocol = "Tcp" 34 | source_port_range = "*" 35 | destination_port_range = "22" 36 | source_address_prefix = "*" 37 | destination_address_prefix = "*" 38 | }, 39 | { 40 | name = "allow-https" 41 | priority = 101 42 | direction = "Inbound" 43 | access = "Allow" 44 | protocol = "Tcp" 45 | source_port_range = "*" 46 | destination_port_range = "443" 47 | source_address_prefix = "*" 48 | destination_address_prefix = "*" 49 | } 50 | # Add more rules as needed 51 | ] 52 | } 53 | 54 | ### Step 2: Create an Azure Network Security Group with Dynamic Security Rules 55 | 56 | Next, use the dynamic block within the azurerm_network_security_group resource to dynamically create security rules based on the nsg_rules variable. 57 | 58 | resource "azurerm_network_security_group" "this" { 59 | name = "example-nsg" 60 | location = "australiaeast" 61 | resource_group_name = azurerm_resource_group.this.name 62 | 63 | dynamic "security_rule" { 64 | for_each = var.nsg_rules 65 | content { 66 | name = security_rule.value.name 67 | priority = security_rule.value.priority 68 | direction = security_rule.value.direction 69 | access = security_rule.value.access 70 | protocol = security_rule.value.protocol 71 | source_port_range = security_rule.value.source_port_range 72 | destination_port_range = security_rule.value.destination_port_range 73 | source_address_prefix = security_rule.value.source_address_prefix 74 | destination_address_prefix = security_rule.value.destination_address_prefix 75 | } 76 | } 77 | } 78 | 79 | In this configuration: 80 | 81 | * The dynamic "security_rule" block iterates over each item in the var.nsg_rules list. 82 | 83 | * The content block inside the dynamic block defines the schema of each NSG rule, using the security_rule.value to access the current item in the iteration. 84 | 85 | * Each attribute of the security_rule is set based on the properties defined in the var.nsg_rules variable. 86 | 87 | ### Step 3: Apply the Configuration 88 | 89 | After defining your variables and resource configuration, you can apply your Terraform configuration as usual with terraform apply. Terraform will dynamically create the NSG rules in the Azure Network Security Group based on the definitions in the nsg_rules variable. 90 | 91 | ![](https://cdn-images-1.medium.com/max/7080/1*WYPkca8YMHN4Hhduv70NDw.png) 92 | 93 | This example demonstrates how dynamic blocks can simplify configurations and make them more flexible by allowing you to define repeatable sections based on the contents of variables. 94 | 95 | Thank you for reading! To explore the complete code for this case, visit the repository [here](https://github.com/chenjd/terraform-101). I hope you find it useful! 96 | -------------------------------------------------------------------------------- /011-terraform-dynamic-blocks/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "this" { 2 | name = "rg-terraform101-dev" 3 | location = "australiaeast" 4 | } 5 | 6 | resource "azurerm_network_security_group" "this" { 7 | name = "example-nsg" 8 | location = "australiaeast" 9 | resource_group_name = azurerm_resource_group.this.name 10 | 11 | dynamic "security_rule" { 12 | for_each = var.nsg_rules 13 | content { 14 | name = security_rule.value.name 15 | priority = security_rule.value.priority 16 | direction = security_rule.value.direction 17 | access = security_rule.value.access 18 | protocol = security_rule.value.protocol 19 | source_port_range = security_rule.value.source_port_range 20 | destination_port_range = security_rule.value.destination_port_range 21 | source_address_prefix = security_rule.value.source_address_prefix 22 | destination_address_prefix = security_rule.value.destination_address_prefix 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /011-terraform-dynamic-blocks/provider.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } -------------------------------------------------------------------------------- /011-terraform-dynamic-blocks/variables.tf: -------------------------------------------------------------------------------- 1 | variable "nsg_rules" { 2 | description = "List of NSG rules" 3 | type = list(object({ 4 | name = string 5 | priority = number 6 | direction = string 7 | access = string 8 | protocol = string 9 | source_port_range = string 10 | destination_port_range = string 11 | source_address_prefix = string 12 | destination_address_prefix = string 13 | })) 14 | default = [ 15 | { 16 | name = "allow-ssh" 17 | priority = 100 18 | direction = "Inbound" 19 | access = "Allow" 20 | protocol = "Tcp" 21 | source_port_range = "*" 22 | destination_port_range = "22" 23 | source_address_prefix = "*" 24 | destination_address_prefix = "*" 25 | }, 26 | { 27 | name = "allow-https" 28 | priority = 101 29 | direction = "Inbound" 30 | access = "Allow" 31 | protocol = "Tcp" 32 | source_port_range = "*" 33 | destination_port_range = "443" 34 | source_address_prefix = "*" 35 | destination_address_prefix = "*" 36 | } 37 | # Add more rules as needed 38 | ] 39 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform 101 2 | 3 | Welcome to the **Terraform 101** repository! This project provides an introductory guide to using Terraform, a powerful tool for building, changing, and versioning infrastructure safely and efficiently. 4 | 5 | ## Overview 6 | 7 | This repository contains a series of examples and exercises designed to help you get started with Terraform. Whether you are a beginner or looking to refresh your skills, you will find valuable resources here to understand the basics of Terraform and its application in real-world scenarios. 8 | 9 | ## Table of Contents 10 | 11 | - [Prerequisites](#prerequisites) 12 | - [Getting Started](#getting-started) 13 | - [Directory Structure](#directory-structure) 14 | - [Usage](#usage) 15 | - [Contributing](#contributing) 16 | - [License](#license) 17 | 18 | ## Prerequisites 19 | 20 | Before you begin, ensure you have the following installed: 21 | 22 | - [Terraform](https://www.terraform.io/downloads.html) 23 | - [Git](https://git-scm.com/downloads) 24 | - A text editor (e.g., VSCode, Sublime Text) 25 | 26 | ## Getting Started 27 | 28 | 1. **Clone the Repository:** 29 | 30 | ```bash 31 | git clone https://github.com/chenjd/terraform-101.git 32 | cd terraform-101 33 | ``` 34 | 35 | 2. **Initialize Terraform:** 36 | 37 | Navigate to any example directory and run: 38 | 39 | ```bash 40 | terraform init 41 | ``` 42 | 43 | 3. **Apply the Configuration:** 44 | 45 | Apply the Terraform configuration to create the infrastructure: 46 | 47 | ```bash 48 | terraform apply 49 | ``` 50 | 51 | 4. **Destroy the Infrastructure:** 52 | 53 | When you're done, clean up the resources: 54 | 55 | ```bash 56 | terraform destroy 57 | ``` 58 | 59 | ## Directory Structure 60 | 61 | The repository is organized as follows: 62 | 63 | ``` 64 | terraform-101/ 65 | ├── example1/ 66 | ├── example2/ 67 | ├── example3/ 68 | ├── ... 69 | └── README.md 70 | ``` 71 | 72 | - `examples/` contains different Terraform configuration examples. Each example is self-contained and can be used independently. 73 | 74 | ## Usage 75 | 76 | Each example in the `examples/` directory includes a `README.md` file with detailed instructions on how to use it. Follow the specific steps outlined in each example to practice and understand different aspects of Terraform. 77 | 78 | ## Contributing 79 | 80 | We welcome contributions! If you have any improvements or additional examples, please follow these steps: 81 | 82 | 1. Fork the repository. 83 | 2. Create a new branch (`git checkout -b feature/your-feature`). 84 | 3. Commit your changes (`git commit -m 'Add some feature'`). 85 | 4. Push to the branch (`git push origin feature/your-feature`). 86 | 5. Create a new Pull Request. 87 | 88 | ## License 89 | 90 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 91 | 92 | --- 93 | 94 | Happy Terraforming! If you have any questions or need further assistance, feel free to open an issue or contact the repository maintainers. 95 | --------------------------------------------------------------------------------