In this first part, you will learn about Infrastructure as Code (IaC) and how to work with Bicep or Terraform to create a simple resource group and storage account. You'll also learn about things like:
- running deployments from the command line
- running deployments from GitHub Actions
- building service principals with federated credentials
To complete this activity, you must have an editor like VSCode, an Azure Subscription with contributor access, and the Azure CLI installed.
Apart from the common concepts of deployment scopes and resource groups there are Terraform specific features that are key to deploying resources. We will explore those in this section:
A provider is a plugin that allows Terraform to interact with cloud, SaaS providers and other APIs. Custom terraform providers can be created if needed. Example of providers are:
- Azure, Google and AWS providers.
- MongoDB, Pager Duty.
- Random, arm2tf.
This is the recommended file structure for a working directory:
- providers.tf: Specifies the providers used in the deployment as well as any configuration for each of them.
- main.tf: Specified the resources being deployed.
- variables.tf: Specifies the variables that will be used to parameterize the deployment.
- outputs.tf Specified any values that will be availabe as an output of the deployment.
You can also use the main.tf file to define all the elements mentioned above, however, that will make your deployments harder to read and maintain.
There are 3 main commands that we will explore in this section:
- terraform init: Initialized the working directory.
- terraform plan: Creates a plan based on the resources specified in your deployment and the resources currently deployed.
- terraform apply: Applies the plan generated by the
plancommand.
To get started, let's create our first Terraform file. The overall goal for this activity is to create the files needed to deploy a storage account. During this activity we will create the recommended file structure mentioned above while learning about using variables and outputs, as well as how to create and use additional files as modules.
Note: for this activity, I'm using VSCode with the Terraform extension. Additionally, I've created a new repository at GitHub which has the starter web application code in it and will be where I'm generating screenshots. For this reason, if you haven't already, you need a GitHub repository where you can store your code and your Terraform files. For simplicity, you can fork this repo: https://github.com/AzureCloudWorkshops/ACW-InfrastructureAsCode_Workshop.
A good way to store this would be similar to the following:
Start by creating a main.tf file. This can be done in a bash terminal, in VSCode, or in PowerShell.
-
Create a folder if you don't have one for
iacand a subfolderTerraform. In the terraform subfolder, create a filemain.tf.Note: If you forked the repo above, you will already have an
iacfolder in that repo.Folder:
mkdir terraform cd terraform mkdir Part1_Main_Module cd iac\terraform\Part1_Main_Module
-
Copy or create the
main.tffile in thePart1_Main_Modulefolder:Create the file
Bash:touch main.tf
or
PowerShell:"" > "main.tf"
or use VSCode:
Right-click on the folder and select New File, name it `main.tf`Note: For Bash and PowerShell, make sure you make directories
mkdirand change directoriescdto the correct location. For VSCode, you can right-click on the folder and select New File, name itmain.tf.
Before moving on, ensure that you have a file called main.tf in a folder called iac\terraform\Part1_Main_Module at the root of your repository (the _workshop repo has starter files and/or you should have made a folder with the main.tf file as shown above).
For this first activity, you'll be creating a simple storage account. To do this easily, you'll want a couple of extensions for Terraform in place in VSCode:
-
Get the Hashicorp Terraform Extension
-
Get the Azure Tools Extension (optional/recommended)
Note: Azure Tools may not be needed, but it's a good idea to have it in place for other things you will do in the future.
-
Specify the providers that we will use in the deployment.
Add the following code to your main.tf file:
terraform { required_version = ">=1.6.6" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~>3.0" } } } provider "azurerm" { features { } }
Before moving on, ensure that you have the main.tf file in the correct folder with the settings as above.
In this step you'll create the storage account resource
-
Add a
resourceblock to create the storage account:Using the text below, change the name of the storage account to something that is unique, such as
iacstgacctYYYYMMDDxzy.Replace
YYYYMMDDwith today's date.Replace
xyzwith your initials.Note: Storage account names must be unique to the world, must be between 3-24 characters, and must be only lowercase characters and numbers.
resource "azurerm_storage_account" "iac_stg_acct" { name = "iacstgacctYYYYMMDDxzy" resource_group_name = "{YOUR RESOURCE GROUP NAME}" location = "{YOUR RESOURCE GROUP LOCATION}" account_tier = "Standard" account_replication_type = "LRS" }Note: The resource group name and location must match the values you provided here.
Before moving on, make sure you have the file in place for the storage account creation.
As mentioned in part 1, there are 3 commands that make up the basic Terraform workflow:
- terraform init
- terraform plan
- terraform apply
The first command only needs to be executed when creating a new configuration or updating an existing one. However, running the command multiple times should not cause any issues.
In this step, you'll deploy the storage account using Terraform.
-
Ensure you are in the correct directory
Before you execute any commands, make sure that you are in the
iac\terraform\Part1_Main_Modulefolder -
Terraform Init Command [terraform init]
Execute the
terraform initcommand.terraform init
After the command completes you should see the following:
-
Terraform Plan Command [terraform plan]
Execute the
terraform plancommand:terraform plan -out main.tfplanNote: the command creates a
main.tfplanfile.You should see the following output (some details are omitted):
-
Terraform Apply Command [terraform apply]
Finally, apply the plan by executing the following command:
terraform apply main.tfplanYou should see the following output:
Before moving on, ensure that you have a storage account in your resource group with a name that matches the value provided in the main.tf file.
As mentioned in part 1, when working with Terraform it is recommended to create separate files to keep everything organized. In this step, you will create a separate providers.tf file.
For the first step, you'll just create a file.
-
Create a new file in the
iac\terraform\Part1_Main_Modulefolderproviders.tf -
Move
terraformandprovidersblocks to providers.tf fileCut the
terraformandprovidersblocks from themain.tffile, and past to theproviders.tffile, this should have no impact on your deployment.
As moving the location of the terraform and providers section should have no impact, it's important to verify this is the case.
-
Confirm there is no impact on your deployment
To confirm execute the
terraform plancommand,terraform plan -out main.tfplan
You should see the following messages:
No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
In Terraform module parameters are referred to as input variables or simply variables, in this part of the workshop you'll create input variables for the storage account name and location. You'll also learn how to use the variables in your deployment.
For starters, we will only add input variables for the resource group name, storage account name and location of the storage account.
-
Add variables for resource group name, storage account name, and location.
Add the following code to the top of the main.tf file:
variable "resourceGroupName" { type = string nullable = false default = "{YOUR RESOURCE GROUP NAME}" } variable "storageAccountName" { type = string nullable = false default = "{YOUR STORAGE ACCOUNT NAME}" } variable "location" { type = string nullable = false default = "{YOUR RESOURCE GROUP LOCATION}" } -
Leverage the variables with
var.<variablename>in themain.tffileUse the variables to populate the storage account values. Terraform references input values using the
varobject. Modify the storage account resource block should to use the variables created above as follows:resource "azurerm_storage_account" "iac_stg_acct" { name = var.storageAccountName resource_group_name = var.resourceGroupName location = var.location account_tier = "Standard" account_replication_type = "LRS" } -
Validate everything is still the same on the plan and state
Execute the
terraform plancommand again, since there were no infrastructure changes you should see this message again:No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
In the previous step you added the ability to use input variables (or parameters) in the terraform template, in this step you will continue with the best practices mentioned above by moving those variable definitions to a separate file.
-
Create a
variables.tffile for variablesAdd a new file
variables.tfto your working directorytouch variables.tf
please note: this filename is just a suggested name and that as long as a file is in the same directory, Terraform will automatically make those variables available in the main module.
-
Put the variables into the
variables.tffileCut the variables from the
main.tffile and paste the variable declarations from the to thevariables.tffile, after the change, the only thing left in themain.tffile should once again be your resource block.
Validate there are still no changes even with the new file structure
-
Execute the
terraform plancommand againSince nothing has changed to update the infrastructure you should again see no changes in the plan.
You might have noticed that so far you haven't been asked to provide a value for the variables during deployment. This is because of the default values that have been put in place in the files.
In this step, you'll see what happens if you try to do a deployment without default/provided values.
-
Remove the default value for the resourceGroupName variable.
In the
variables.tffile, delete the linedefault = "{YOUR RESOURCE GROUP NAME}", but leave everything else in place. -
Execute the
terraform plancommandWith the change, run the plan again:
terraform plan -out main.tfplan
you should see this prompt:
This is obviously not the most efficient deployment strategy and is also error prone, we will look at a better deployment option in the next step.
Enter your resource group name to save the plan.
In this step, you will use a special file called a variable definitions file to specify the values you want to use in the deployment.
-
Add a file called terraform.tfvars to your working directory.
Create the file:
touch terraform.tfvars
Terraform automatically scans for this specific file when deploying resources.
Note: If you want to use a different file name you would need to also use the
-var-fileparameter when executing theplancommand. -
Move variables to the new file
Type the name of any of the variables declared in the
variablesfile, if you are using the Terraform extension for Visual studio code you should see something like this:Provide a value for all the variables.
-
Execute the
terraform plancommand again.With all of the variables defined and then given a value, you have seen how to separate the variable definitions from the values for powerful template deployment configurations.
To be clear, you should not be prompted to provide a value for and variables and you should still see no changes to your deployment in the plan since no infrastructure changes are taking place.
You now have a file that you can reuse in multiple resource groups with various storage account names (you would need to change the name in the parameter file at this point to ensure it is unique).
Up until now, you have used variables to provide the name and location of the resource group that contains the storage account. However, Terraform has another way to access information defined outside of Terraform or that is part of a different deployment using data sources.
In this step, you will modify the files you currently have to use a data source to access the resource group information instead of providing the values through variables.
In this first step, you'll add a data source for the resource group
-
Add the
data_rgresource group declarationGo to the top of the main.tf file and type
da, if you are using the Terraform extension for VS code you should see the following:Hit the tab key, a data block will be created automatically:
-
Change the type to
azurerm_resource_groupType
azurerm_resource_group,azurerm_resource_group
At this point, autocomplete should display the option after you type a few characters:
-
Set the name of the resource group and location for the resource group
The resource group data source requires the name of the resource group. You can use the resourceGroupName variable to populate the parameter on this data source. The new data block should look like this:
data "azurerm_resource_group" "data_rg" { name = var.resourceGroupName }
In this step, you'll update the declaration in the storage account to leverage the resource group data source.
-
Replace the
resource_group_nameandlocationYou can now replace the
resource_group_nameandlocationparameters in the storage account block with the values from the data source using the following syntax:data.{RESOURCE_TYPE}.{DATA_SOURCE_NAME}.{DATA_SOURCE_PROPERTY}For example, to access the name of a resource group with a data source:
data.azurerm_resource_group.data_rg.nameand
data.azurerm_resource_group.data_rg.location -
Validate the file text before proceeding
After replacing the name and location of the resource group the storage account block should look something like this:
resource "azurerm_storage_account" "cm_stg_acct" { name = var.storageAccountName resource_group_name = data.azurerm_resource_group.data_rg.name location = data.azurerm_resource_group.data_rg.location account_tier = "Standard" account_replication_type = "LRS" }
In this step you'll remove the location variable and execute the deployment
-
Remove the
locationvariableSince we are now getting the resource group information from the data source we can now remove the
locationvariable from thevariables.tfand the value assignment from theterraform.tfvarsfile.Note: the state of the system already knows the resource group exists. In this template, the resource group would not be created since no location is provided if there were not currently a state that holds the value of the current existing resource group.
-
Execute the plan and validate there are no changes
You can now execute the
terraform plancommand again, since we are not adding or removing any resources you should see a message saying that no changes were detected.
In this module you will learn to use local variables and functions to create a unique string name for the storage account name. The term local variable in terraform refers to any variable used inside a module.
Since the storage account name needs to be unique across all resources in Azure you will now add a unique identifier section to the storage account name to comply with this requirement.
-
Add a
uniqueIdentifiervariable to your variables file.In the
variables.tffile, add a new variable as follows:variable "uniqueIdentifier" { type = string nullable = false default = "20291231acw" } -
Add the value in the
terraform.tfvarsfile.Based on the value you used earlier, add the variable in the
terraform.tfvarsfile with the following format: YYYYMMDDabc. However, for this exercise you want a second storage account so change the date by one day to make it unique from the other storage account.For example, if the value was
20291231acwfor the first one, creat the second one as follows:uniqueIdentifier: 20291230acw -
Repeat the previous two steps to create the base storage account name as a varaiable.
In the
variables.tffile, add the following code:variable "storageAccountBase" { type = string nullable = false default = "iacstgacct" }Then add the value to the
terraform.tfvarsfile:storageAccountBase = "iacstgacct"
Local variables in Terraform are declared using a locals block. In this step, you'll add a local variable for the full name of the storage account.
-
Add a locals block to your
main.tffileAt the top of the
main.tffile, add a variable calledstorageAccountNameFull. Assign a value by concatenating thestorageAccountNameanduniqueIdentifiervariables using interpolation:locals { storageAccountNameFull = "${var.storageAccountBase}${var.uniqueIdentifier}" } -
Define the second storage account
Add another storage account resource block by copying the existing one and replacing the
nameparameter with the local variable you just created, the following syntax is used to access local variables:local.{YOUR VARIABLE NAME}So the overall block should be similar to this (Note the resource declaration uses
iac_stg_acct_fullhere as well):resource "azurerm_storage_account" "iac_stg_acct_full" { name = local.storageAccountNameFull resource_group_name = data.azurerm_resource_group.data_rg.name location = data.azurerm_resource_group.data_rg.location account_tier = "Standard" account_replication_type = "LRS" } -
Execute the plan to validate you will be creating a new storage account
Execute the terraform plan command again, you should see a message saying that 1 resource will be added.
terraform plan -out main.tfplan
- Execute the apply command to create the storage account
Execute the terraform apply command
terraform apply main.tfplan
Once the command is completed you should see the new storage account in your resource group.
If you completed the Bicep section of this workshop, you will recall that the uniquestring function allows you to generate a string that can help make resource names unique.
In this step, you will use a provider to access create a unique variable string in Terraform.
-
Create a random object
Add the following after the
azurermelement in therequired providersportion of theproviders.tffile:random = { source = "hashicorp/random" version = "3.6.2" }Reminder: if at any point you get lost, don't forget to check the solution files for help in locating where something should go
-
Modify the
main.tffile to generate a unique stringGenerate a unique id by adding the following block to your
main.tffile:resource "random_string" "random" { length = 10 special = false lower = true upper = false }Note: The name for storage accounts does not allow any special characters so we are forcing the generated string to comply to this. You can check the documentation for the random provider for additional configuration options.
-
Add a new local variable called
storageAccountNameUniqueIn the
main.tffile, in thelocalsblock, under the currentstorageAccountNameFull, add a new local variablestorageAccountNameUnique, and assign the following value:storageAccountNameUnique = "${var.storageAccountName}${var.uniqueIdentifier}${random_string.random.result}" -
Create the resource for a third storage account.
Add a new storage account resource block and assign the name using the new variable created. Use the new local variable for the storage account name.
resource "azurerm_storage_account" "iac_stg_acct_unique" { name = local.storageAccountNameUnique resource_group_name = data.azurerm_resource_group.data_rg.name location = data.azurerm_resource_group.data_rg.location account_tier = "Standard" account_replication_type = "LRS" } -
Re-initialize Terraform with the
terraform initcommandSince we added a provider to our deployment, we need to run the
terraform initcommand before we can create a new plan, otherwise you'll see the following message:Make sure all your files are saved, then run the command:
terraform init
-
Deploy using the
planandapplycommands.Deploy the new resource by executing the
planandapplycommand, you should see 2 resources being created in the plan: one for the uniqueid and one for the new storage account.terraform plan -out main.tfplan terraform apploy main.tfplan
Important: - This deployment will fail (and that's expected)
Your deployment should fail due to the length of the new storage account name being over 24 characters, in the next step we will use one of the built-in functions in Terraform to solve the problem.
Terraform has multiple built-in functions that can be used to transform and combine values, you can find a full list here: https://developer.hashicorp.com/terraform/language/functions.
In this step, you will use the substr function to truncate the length of the storage account name:
- Leverage the
substrfunction
The substring function has the following format:
substr(string, offset, length)
-
Use the
substrfunction to ensure the value of thestorageAccountNameFullUniquevariable is a maximum of 24 characters.Change the declaration in the
localssection ofmain.tfto the following for thestorageAccountNameUnique:storageAccountNameUnique = substr("${var.storageAccountName}${var.uniqueIdentifier}${random_string.random.id}",0,24) -
Run the
planandapplycommands again as above.Run the commands:
terraform plan -out main.tfplan terraform apploy main.tfplan
This time, you should see your storage account created as expected in the portal:
Input variables allow your Terraform files to be dynamic but without any validations there is also the risk that the values provided will result in invalid properties for your resources. For this reason, Terraform provides different ways to validate your configuration.
In this step, you will add validations to a couple of input variables to prevent any issues with planning and/or deployment.
Variable validations are added using the validation property of a variable.
For example:
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
-
Validate the
storageAccountNamevariableAdd the following validation block to the
storageAccountNamevariable in thevariables.tffile:validation { condition = length(var.storageAccountName) > 3 error_message = "The storage account name should be at least 3 characters" }For a resulting block of:
variable "storageAccountName" { type = string nullable = false default = "iacstgacct20291231acw" validation { condition = length(var.storageAccountName) > 3 error_message = "The storage account name should be at least 3 characters" } } -
Prove that the validation is working as expected.
In the
terraform.tfvarsfile, assign the valuecto thestorageAccountNamevariable.Try to execute the plan.
terraform plan -out main.tfplan
You should see the following message:
-
If time permits, add 2 more validations:
- Validate that the
uniqueIdentifiervariable is11characters long. - Add an
environmentinput variable that will allow only two (2) values (hint: look at the contains function):devandprodand use that to create a new storage account resource.
Reminder: You can also review the solution files for additional help.
- Validate that the
You can now deploy the same file to different resource groups multiple times and it will create a unique storage account name per resource group (and per environment if needed) using local variables, built-in functions and validations.
If time permits, you can try creating a different resource group and use what you have built so far to deploy these resources there.
So far, we have been working completely out of our main module. In application deployments like the one you will do in part 2, you will want to be able to have your resources distributed in modules.
In this task, you'll learn about modules using a similar deployment to the current deployment.
In your previous deployment you were using information from an existing resource group, however, in a real world scenario you might have to create the entire infrastructure including the resource group.
In this step, you will create a new template that deploys a new resource group.
Now it's time to see how much you can remember from above!
-
Create a new folder for this second deployment
Create a new folder under the
terraformfolder calledPart1_Modulesand add the following files:- providers.tf
- variables.tf
- main.tf
- terraform.tfvars
-
Leverage what you know to create a valid
providers.tffileAdd the following providers to the
providers.tffile:azurermrandom
-
Create variables for
resourceGroupNameandlocationAdd the following variables to
variables.tf:- resourceGroupName: string type, not nullable.
- location: string type, not nullable, validation to only allow
East USas a value (optional).
-
Leverage the
terraform.tfvarsfile to create values for the variablesAssign values to the variables in the
terraform.tfvarsfile.- resourceGroupName:
iac-training-rg-modules - location:
eastus
- resourceGroupName:
-
Use the
azurerm_resource_groupto define the group inmain.tfAdd a resource group block to the main.tf file, the resource type needed is
azurerm_resource_group. Leverage the variables for name and location created above. -
Deploy the resource group
Run the commands to get your deployment into the cloud.
Hint: Don't forget to initialize Terraform in the new directory!
terraform initReminder: If at any point you get lost, take a look at the files in the solutions folder to see if you can get
unstuck.
Now that you have a new resource group, your next task is to create a module for the storage account.
In Terraform, you can create your own modules locally or you can get modules from the Terraform registry, you can even publish your own modules for other people to use.
For this exercise, you will focus on local modules.
-
Add a modules folder in your working directory and add a storageAccount inside of it.
In the current folder where you started your new resource group, add the subdirectory
storageAccount -
Create
main.tfandvariables.tffiles in thestorageAccountfolder.Add variables for the storage account name composition with base, uniqueIdentifier, and environment
storageAccountNameFull:storageAccountNameFullHint: The local
variables.tffile for the nested storage account should only need three variables defined. -
Create a new storage account definiation in the
main.tffile in thestorageAccountfolder using terraformLeverage any of the storage account resource blocks and learning you've done from the root module created in the previous exercise and replace the values assigned to the
name,resource_group_nameandlocationwith variables.Hint": Use the following variable names, and make sure they are defined as variables for use in the terraform:
- storageAccountNameFull (composed from base + uniqueIdentifier + environment)
- resourceGroupName
- location
Additional Hint: The storageAccountNameFull variable will be passed in so it should not have a default value.
-
Where is the
terraform.tfvarsfile?You will only need the top-level
terraform.tfvarsfile, there is no need to nest one in thestorageAccountfolder. This allows you to share the variables among all modules, defined in only one place with values. -
Add the variables specified or necessary to create the variables specified in the previous step to the top-level
variables.tffile.Remember, you will be composing the
storageAccountNameFullvariable, so that should not be defined in thevariables.tffile (what variables do you need to define?).Hint: There are three variables needed to define the full storage account name that will also be combined with a random string.
With that in mind, there should be five variables defined in the
variables.tffile. What do you think they are?Take a minute to think about this. If you get stuck, you can always refer to the solution files.
-
Define the values for the variables in the
terraform.tfvarsfileYou will need to make sure the defaults are set for all the variables.
Hint: Since the name of the storage account is confined and we want it to be a combination of
base+unique+env+random, and we know unique is11chars andenvis either3or4chars, keep your base storage account to greater than3and less than8chars or there will be no room for the random string to append any chars to the end.Suggestion, use something like this:
storageAccountNameBase: iacstgFinal Hint: Your terraform.tfvars should define values for the five variables, none of which should be named
storageAccountNameFull. Where do you think you need to define that variable? -
Add the new storage account as a module.
Add a reference to the storage account module by adding the following block in the top-level
main.tffile, under the declaration for the resource group:module "storageAccount" { source = "./storageAccount" storageAccountNameEnv = local.storageAccountNameFull resourceGroupName = var.resourceGroupName location = var.location }The main parameter you need to supply when using modules is the
sourcedirectory where the module'smain.tffile is defined (in this case, it should just be nested instorageAccount).For local modules the
sourcevalue should be populated with the./<folderpath>notation.Hint: The module will be created when you initialize terraform
You also need to pass values for any variables that the module is expecting from the top-level. In this case, you'll need to compose the full storage account name in a local variable for the
main.tffile.Hint: This was done in the previous sections using a
localssection. Make sure to include theenvironentas part of the name composition! -
Deploy the solution
As mentioned previously, using the module requires a quick compilation. For that reason, what should you do first when getting ready to deploy?
Hint: The answer is not
planorapplyIf you run the wrong command here, you'll see an error:
Run the commands to initialize, plan, and apply the changes. When this is done, you should have a new storage account that follows the pattern
base+uniqueIdentifer+environment+randomstring. -
Validate the solution
Make sure the new resource has been successfully deployed before moving on.
There is one more Terraform element that you have not worked with yet: outputs.
When creating resources, some of the properties of those resources are exported so you can use them as parameters for other steps in your deployment. In this step, you will export some of the storage account properties as outputs to be leveraged in other modules.
In this task, you'll learn how to use outputs from the storageAccount module to get properties of the storage account to another module for creating a storage account container.
Note: Yes, in the real world you could create them together in the same file. Please note that for academic reasons, separation of the container to a new file is used here for demonstration purposes.
In this step you'll create an outputs.tf file and use that to get values from the storageAccount module.
-
Create an
outputs.tffile in the storage account module directory.Navigate to the storage account module directory and create a new file
touch outputs.tf
-
Export the storage account properties
name,id, andlocationfor use in other modules.Add 3 output blocks to the newly created file: account name, id and location. The following is an example of the output block for the account name:
output "{OUTPUT_VARIABLE_NAME}" { value = azurerm_storage_account.{YOUR_STORAGE_ACCOUNT_RESOURCE}.name }For example:
output "storageAccountName" { value = azurerm_storage_account.iac_stg_acct_module.name }Repeat the outputs for the remaining account properties
idandlocation. -
Validate that nothing has changed in the plan.
Run the
terraform plancommand to validate there are currently no changes to apply.Since we are not updating the infrastructure executing the
planandapplycommands will have no effect, we will do that in the next step.
With the outputs generated by the storage account resource, you can now leverage the values to create a storage container resource.
Reminder: This is an academic exercise and in the real world you would likely create the storage account container along with the storage account in the same module.
-
Create a new folder inside your
modulesfolder calledstorageContainer.At the same level as the
storageAccountfolder, create a new foldermkdir storageContainer
-
Add a
main.tfand avariables.tffile.Just as with the storage account creation, you'll need the two files for module and variable declarations:
cd storageContainer touch main.tf touch variables.tf -
Add 2 variables:
storageAccountNameandcontainerName.In the variables.tf file, add the two variables:
variable "storageAccountName" { type = string nullable = false } variable "containerName" { type = string nullable = false } -
Add an
azurerm_storage_containerresource block in themain.tffile.In the
main.tffile, add a new resourceazurerm_storage_containerPopulate the container properties with the variables you created. Make sure to name the resource something useful like
images_containeror whatever you think you might name your container.For example:
resource "azurerm_storage_container" "images_container" { name = var.containerName storage_account_name = var.storageAccountName }Reminder: if you get lost, leverage the solution files.
-
Modify the
rootlevel variables to contain a variable for the newcontainerName.Add a
containerNamevariable to the input variables of therootfolder'svariables.tffile:variable "containerName" { type = string nullable = false } `` -
Add a default value to the
terraform.tfvarsfile in therootfolderIn the root folder, add a default name
imagesfor the container to theterraform.tfvarsfile:containerName = "images" -
Modify the
main.tffile in the root to add the deployment of thestorageContainermodule.Add a
storageContainermodule block to the root module and populate thestorageAccountNamewith the output value from thestorageAccountmodule:module "storageContainer" { source = "./storageContainer" containerName = var.containerName storageAccountName = module.storageAccount.storageAccountName } -
No deployment yet by design.
There is one final (optional) thing to do to get the deployment to work more professionally, which you will do in the next and final task.
As you already know, the state file is key for Terraform to know what changes need to be done to the infrastructure. This state file can be saved in different locations.
Up to this point, we have been using the local backend, but in this step we will add a remote backend which will be needed for automated deployments.
Additionally, this will make sure that the state is not only saved on one developer's machine.
To make this easy, you'll leverage an Azure storage account for your state files. You will need to make sure you understand this part well, because it will be critical to the next part where you will do a full resource deployment to Azure.
Important: This storage account and container name are new and are not any that you've previously created, nor should you try to create this with terraform at this time. You only need this once and in the interest of time you should just manually provision the account and container with new values in a new resource group.
Go to the Azure Portal and create the following resources (remember to replace the YYYYMMDDxxx with the date and your initials):
- RG:
rg-terraform-github-actions-state - Storage Account:
tfghactionsYYYYMMDDxxx - container:
tfstatepart1 - performance:
standard - redundancy:
LRS
Note: Each deployment state will leverage a unique container. This will allow you to reuse this same storage account in the next parts of this workshop for automated deployments.
In this step, you'll upload your current terraform.state file to the storage container.
-
Open the container at Azure
In the Azure portal, open the container, then select
Uploadto upload the fileImportant: After upload, you should see it in the container at Azure:
Hint: you'll need to browse to the folder where you were running the
applycommands earlier.
In this step, you'll tell your local deployment to use the Azure storage account when interactions with the state file need to happen:
-
Modify the
providers.tffile in the root folder to add the provider.Open the
providers.tffile and add this block after therequired_providerssection of the mainterraformdeclaration:backend "azurerm" { resource_group_name = "rg-terraform-github-actions-state" storage_account_name = "{YOUR_STORAGE_ACCOUNT_NAME}" container_name = "tfstatepart1" key = "terraform.tfstate" use_oidc = true }
Note: Make sure to put the correct storage_account_name and container_name in your backend provider declaration.
To complete this first part, it's time to do the final deployment and leverage the state file in the azure storage account.
-
Reinitialize Terraform
With the backend configuration set to Azure, you need to execute the
terraform initcommandterraform init
-
Run the plan
Execute the
terraform plancommandterraform plan -out main.tfplan
Since we never created the container above, you should see the changes for that showing in the plan now.
-
Deploy
Deploy the
terraform applycommandterraform apply main.tfplan
-
Validate your storage container is in place and everything is now working to complete this module
You should now be using the state file in the Azure storage account and you will also have created the storage account with container as expected from the module that leveraged the outputs above.
CONGRATULATIONS!!!!
At this point you have learned most of the basic concepts you will need to work with Terraform for infrastructure deployments.
Make sure that all the files were created successfully and that you can re-run your deployments at will before proceeding.
The repetitive and consistent nature of the deployments are some of the main reasons you want to use Infrastructure as Code.
In this first part of the workshop, you learned how to work with Terraform to create storage account resources in a resource group. Along the way you learned the following concepts:
- creating terraform files
- running deployments from the command line
- using input and local variables
- using data sources
- using functions
- using modules
- using outputs
Remember that you can review the solution files to validate your solution looks mostly similar.
You are now ready to move on to the next part:
Part2-BuildingTheInfrastructure
Make sure to take care of the common tasks first, then after completing the common tasks, move to the Terraform specific walkthrough for part 2.































