<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Luke Orellana</title>
    <description>The latest articles on Forem by Luke Orellana (@lukeorellana).</description>
    <link>https://forem.com/lukeorellana</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F311876%2F0b7c0a14-de69-43e8-9667-d78e16e96dc0.jpg</url>
      <title>Forem: Luke Orellana</title>
      <link>https://forem.com/lukeorellana</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lukeorellana"/>
    <language>en</language>
    <item>
      <title>Getting Started with Terraform on Azure: Tips and Tricks</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Sun, 16 Aug 2020 19:46:27 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-tips-and-tricks-4afo</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-tips-and-tricks-4afo</guid>
      <description>&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Modules&lt;/li&gt;
&lt;li&gt;Remote State&lt;/li&gt;
&lt;li&gt;Source Control&lt;/li&gt;
&lt;li&gt;Keeping Designs Simple and Reusable&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure development is complex, and there can be many hoops to jump through. Making changes to live infrastructure code always involves some risk and can feel like a game of Jenga. While Terraform is relatively new (initial release in  2014), several proven practices are known in the Terraform community that help deal with some hurdles and complexities. Understanding the trial and errors of those who used Terraform early on allows us to learn from them and be more efficient when we are just starting.  This knowledge increases the chance of success in implementing and using Terraform. &lt;/p&gt;

&lt;p&gt;In this guide, we will review some practical tips and tricks to be mindful of when developing with Terraform. Although these are community proven practices, keep in mind that there is more than one way to do something, and it doesn't necessarily mean that's the best and most efficient way for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In the software development world, we break up reusable segments of our code into parameterized functions and reuse them. This practice allows us to write tests for these functions and maintain them. In Terraform, we use modules in the same manner. We make templates of infrastructure and convert them into modules, which allows the code in each module to be reusable, maintainable, and testable. &lt;/p&gt;

&lt;p&gt;Do not create Terraform configurations that are thousands of lines of code. It reduces code quality and clarity when debugging or making changes. Splitting up your infrastructure code into modules will also prevent you from copying and pasting code between environments, which can introduce many errors. &lt;/p&gt;

&lt;h3&gt;
  
  
  Version Providers and Modules
&lt;/h3&gt;

&lt;p&gt;The Azure Terraform provider is changing extremely fast. Check out the &lt;a href="https://github.com/terraform-providers/terraform-provider-azurerm/blob/master/CHANGELOG.md"&gt;change log&lt;/a&gt; for the Azure provider. The amount of changes made every month is extreme, and many code-breaking changes appear in many updates. To guard yourself against this, version your provider and save yourself the headache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Additionally, version your modules, especially ones from the Terraform Registry. If you're developing private modules, version those as well. Versioning modules allow for introducing module changes without affecting the infrastructure that is currently using them. For example, let's say my current environment uses version 1.1 of my server module, which is stored in a repo and tagged with v1.1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Server
module "server" {
  source    = "git::https://allanore@dev.azure.com/allanore/terraform-azure-server/_git/terraform-azure-server?ref=v1.1"

  servername    = "myserver123"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I need to add a new feature to the module that includes Private Link functionality. In this case, I can use module versioning to safely deploy infrastructure using the new version without affecting infrastructure using version 1.1 by tagging it as version 1.2 and sourcing the specific module version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Server
module "server" {
  source    = "git::https://allanore@dev.azure.com/allanore/terraform-azure-server/_git/terraform-azure-server?ref=v1.2"

  servername    = "myserver211"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
  private_link = true
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using versioning for both providers and modules is a must in Terraform, and you will quickly find out why if your not using them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Dependency Injections
&lt;/h3&gt;

&lt;p&gt;When reusing modules throughout different environments, some environments may contain required components that already exist. For example, if I write a module that requires a storage account for the service that it's deploying, there may be some environments where this storage account already exists. This scenario may cause some people to attempt to write logic into their code to check if a resource exists or not and perform X action if it does. Introducing complex logic like this is not in line with the declarative methodology that Terraform uses. The resource either exists or not. &lt;/p&gt;

&lt;p&gt;Instead, use dependency injections. Create the module to allow input from resources that either already exist or are created in the configuration.&lt;br&gt;
Take a look at the code below, for example. We have a Network Security Group module that requires a subnet ID to associate the NSG to a subnet. In this example, we are creating the subnet within the same configuration and passing it along. The subnet does not exist prior, so we are creating one to assign to the NSG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Subnet is directly managed in the Terraform configuration

resource "azurerm_subnet" "snet" {
  name                 = "snet-cloudapp-1"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = "10.0.2.0/24"
}

module "nsg" {
  source                = "./modules/nsg"
  resource_group_name   = azurerm_resource_group.rg.name
  nsg_name              = "nsg-${var.system}-httpsallow"
  source_address_prefix = ["VirtualNetwork"]
  predefined_rules = [
    {
      name     = "HTTPS"
      priority = "500"
    },
    {
      name     = "RDP"
      priority = "501"
    }
  ]

  subnet_id = azurerm_subnet.snet.id
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Alternatively, we have another environment where a subnet is already existing. We would use the &lt;code&gt;azurerm_subnet&lt;/code&gt; data source to collect the subnet id information and pass it through to our module using &lt;code&gt;data.arurerm_subnet.snet.id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Subnet already exists and is called with a data source block

data "azurerm_subnet" "snet" {
  name                 = "snet-coudapp-1"
  virtual_network_name = "production"
  resource_group_name  = "rg-networking"
}

module "nsg" {
  source                = "../../modules/nsg"
  resource_group_name   = azurerm_resource_group.rg.name
  nsg_name              = "nsg-${var.system}-httpsallow"
  source_address_prefix = ["VirtualNetwork"]
  predefined_rules = [
    {
      name     = "HTTPS"
      priority = "500"
    },
    {
      name     = "RDP"
      priority = "501"
    }
  ]

  subnet_id = data.azurerm_subnet.snet.id
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We are not hard coding logic into the module to check for an existing subnet in these two examples. Instead, we take the declarative approach that Terraform is designed for and state in our configuration if it already exists or if it doesn't. Our module can now be reusable in different situations, and we are not complicating the module. We also have better visibility in the module code. Another co-worker on the team can look at the module and get a clear distinction between the two environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote State &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Try to use &lt;a href="https://cloudskills.io/blog/terraform-azure-04"&gt;remote state&lt;/a&gt; as soon as possible in your Terraform development. It can save many headaches later on, especially when multiple people become involved with deploying and managing the same Terraform code. &lt;/p&gt;

&lt;p&gt;Using remote state allows us to secure sensitive variables in our configurations. The Terraform state file is not encrypted, so keeping it on a local workstation may quickly become a security issue. Also, don't make a habit of storing Terraform state files in source control. It increases the chance of exposing sensitive variables, especially if the repository is public. Instead, use a &lt;a href="https://git-scm.com/docs/gitignore"&gt;gitignore&lt;/a&gt; file to omit any tf.state files from accidentally getting committed automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Split Up Terraform States
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.slideshare.net/opencredo/hashidays-london-2017-evolving-your-infrastructure-with-terraform-by-nicki-watt"&gt;Terraservices&lt;/a&gt; is a popular term coined a few years ago which involves splitting up Terraform state into different environments to reduce the blast radius on changes made. You don't want to keep all your eggs in one basket. Let's say a team member makes a change to resize a VM. They end up fat fingering the resource group name, and their pipeline workflow auto applies the incorrect change. Terraform rebuilds the resource group and deletes all items causing catastrophic failures to the environment. This situation is not uncommon. &lt;/p&gt;

&lt;p&gt;Also, be aware that your Terraform plan becomes longer and longer if you don't split up a reasonably large environment into separate states. It introduces a new type of risk. Now, the Terraform plan can take longer to run and become harder to read as there are more resources affected by the change. It also means unwanted changes can be easily missed. It's easier to catch a mistake in a few lines of code vs. 10000 lines.&lt;/p&gt;

&lt;p&gt;Ideally, you want to separate high-risk components from components that are typically changed and modified. Don't keep all the eggs in one basket. Below is a Terraform project folder structure inspired by &lt;a href="https://blog.gruntwork.io/how-to-manage-terraform-state-28f5697e68fa"&gt;Gruntwork's recommended setup&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prod
  └ rg
    └ main.tf
    └ variables.tf
    └ output.tf
  └ networking
  └ services
      └ frontend-app
          └ main.tf
          └ variables.tf
          └ output.tf
      └ backend-app
  └ data-storage
      └ sql
      └ redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the folder structure above, each folder separates out the Terraform states. The resource group has its own state, limiting the risk of daily changes made to the resource group. Services like SQL and Redis are also separated to reduce the risk of accidentally modifying the databases on any change. Splitting up environment states like this reduces a lot of risks. However, it adds a lot of complexity to the infrastructure code. We now have to design ways to feed information between each state and deal with dependencies. Terraform currently doesn't allow for an easy way to manage this. But, tools like &lt;a href="https://terragrunt.gruntwork.io/"&gt;Terragrunt&lt;/a&gt;, developed by Gruntwork, address handling the complexities with splitting up Terraform state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Control &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform and source control go together hand in hand. If you're not storing your Terraform code in source control, you're missing out on the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change Tracking&lt;/strong&gt;: The historical data of all infrastructure changes is extremely powerful and a great bonus for auditors or compliance requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback&lt;/strong&gt;: One of the major benefits of infrastructure as code is the ability to "rollback" to the previous configuration or state. However, depending on the environment, that rollback may mean a rebuild. Storing the Terraform configuration in source control makes it easier to re-deploy a pre-existing working state of the environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration Among Teams&lt;/strong&gt;: Most source control tools like Azure DevOps, Github, or Bitbucket provide a form of access control. This role-based access allows for separate teams to manage their infrastructure code or provide read-only access to other teams for increased visibility of how the environment works. No more guessing if a firewall port is open or not; look at the code and see if it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: There are many CI/CD tools available that hook into source control. These tools amplify the development and deployment of Terraform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also the concept of &lt;em&gt;GitOps&lt;/em&gt;, where processes are automated through Git workflows like submitting a pull request. There are community tools out there like &lt;a href="https://www.runatlantis.io/"&gt;Atlantis&lt;/a&gt; that are amazing for GitOps with Terraform and can increase efficiency among teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structure Repo to Business Needs
&lt;/h3&gt;

&lt;p&gt;Designing the source control repo structure for infrastructure can be an intimidating task, especially for those making the jump from a traditional systems engineer to an infrastructure developer role. There are various strategies for storing Terraform code. Some companies put all their Terraform configurations into a single repository, some store configurations with each project's application source code. So which one should I pick?&lt;/p&gt;

&lt;p&gt;This short answer is, it depends on your environment. Large environments are going to have a completely different set up than start-up environments. Also, team structure comes largely into play here. Do you have a team that manages all the infrastructure, or is it the developers and DevOps engineers who manage the infrastructure for their application? You will see many DevOps experts and thought leaders in the community talk about &lt;a href="https://www.thoughtworks.com/insights/blog/applying-conways-law-improve-your-software-development"&gt;Conway's Law&lt;/a&gt;, which states that the communication structure of organizations is the limiter on the way that they develop and design software. This concept is pretty evident when implementing Terraform into your organization. Analyze how your teams are structured and structure your Terraform configuration repos in a way that compliments that structure.  &lt;/p&gt;

&lt;p&gt;Here are several common repo strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single Repo:&lt;/strong&gt;: All live infrastructure code is in one single repository managed by a governing team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One Repo Per Project:&lt;/strong&gt; Every application has its own Terraform folder, and code is stored in a folder of the application source code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One Repo Per Environment:&lt;/strong&gt; Environments are split up into their own repository and managed by separate teams. For example, code managing the company firewalls are in a separate repo and managed by the security or networking team. This strategy allows each team to own and manage their infrastructure responsibilities and delegate out lesser permissions for other teams to request changes or view the environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't stress out over getting your Terraform repo structure right when your first starting out. This will most likely change several times due to business needs, scaling up, or finding a better solution for your environment. Starbucks changed up its repo structure three times over several years and ended up settling &lt;a href="https://www.hashicorp.com/resources/terraform-at-starbucks-infrastructure-as-code-for-software-engineers/"&gt;on a repo per component&lt;/a&gt; strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep All Live Infrastructure in the Master Branch
&lt;/h3&gt;

&lt;p&gt;All live infrastructure changes should always stay in the master branch. Storing the same infrastructure code in multiple branches can cause conflicts and create headaches. For example, let's say a team member branches off of master and adjusts the Terraform configuration to change a VM's size. They make their change and deploy it, but don't merge their branch back into master because they are still making changes. A few minutes later, someone else modifies the same VM's tags but creates a different branch off of master that hasn't been updated yet with the new VM size. The change to the tags is deployed, and now the VM size is reverted back to its original size because it didn't contain the VM resize code. This is why it's important to make sure the master branch is always a live representation of the environment. &lt;/p&gt;

&lt;h3&gt;
  
  
  Execute Terraform Code Through a Pipeline
&lt;/h3&gt;

&lt;p&gt;When first starting on Terraform, it is typical to have each infrastructure developer manage the infrastructure by authenticating locally on their machine with the Azure provider (either with AZ Cli or some environment variables). They execute the Terraform code with their local install of Terraform. Long term, this can cause a few headaches like inconsistent Terraform versions among developers. It's best to shift to deploying code with a pipeline by storing Terraform configurations in source control and running a Continuous Integration process that executes the Terraform code on pull requests. A pipeline significantly increases automation capabilities and has a few advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform code is run on the same platform every time, reducing errors due to inconsistent dependencies like Terraform versions.&lt;/li&gt;
&lt;li&gt;Pipelines can introduce configuration error checking and Terraform policy, preventing insecure or destructive configurations changes from being made.&lt;/li&gt;
&lt;li&gt;Automated testing can run to perform regression tests against modules when a new change is made to the modules.&lt;/li&gt;
&lt;li&gt;Many pipeline tools provide some sort of secret store functionality that makes it easy to securely pass variables through to Terraform configurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping Designs Simple and Reusable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It's essential to keep the right balance between creating conditional logic and introducing too many complexities. For example, it may be useful to add logic into a networking module that will automatically choose the next available subnet space on a Virtual Network and create a subnet. While this logic prevents a user from having to specify a subnet address when they use the module, it also adds more complexity and can make the module more brittle. It may be better to design the module to contain an argument to take in input for the subnet address, requiring the user to calculate a subnet address for the module input beforehand. These are trade-offs with pros and cons to each.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;code review&lt;/em&gt; is a software development practice where multiple developers check each other's code for mistakes. With infrastructure development, this is starting to become a more common practice.  Complex Terraform code will take away from the benefits of code reviews.  When peers cannot easily understand the code to review, errors can be easily missed.  &lt;/p&gt;

&lt;p&gt;Complex Terraform code will also make it harder to troubleshoot issues and onboard new people to the team. One of the benefits of IaC is the living documentation that it provides. Don't put in logic that makes infrastructure code too complex to use for documentation. &lt;/p&gt;

&lt;p&gt;Connecting inputs and outputs between modules and states can introduce many complexities and can grow to become a dependency nightmare. When passing data between modules or state files, be mindful of the purpose and limit the dependencies involved in your design. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use Provisioners Sparingly
&lt;/h3&gt;

&lt;p&gt;Most provisioners introduce platform or network constraints into our Terraform code. For example, using a provisioner to SSH into a server once it's provisioned and run a script will now require the node executing the Terraform code to have network access to the VM during deployment.&lt;/p&gt;

&lt;p&gt;Instead, take advantage of Azure's custom script extension for VMs to &lt;a href="https://github.com/CloudSkills/Terraform-Projects/blob/master/10-Advanced-HCL/6.%20Dynamic_blocks/main.tf"&gt;pass a script through to the VM&lt;/a&gt; without any network constraints. &lt;/p&gt;

&lt;p&gt;The goal is to create infrastructure code that you can execute from anywhere. Aim to achieve this as much as possible to give your design even more reusability. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use Terraform Graph to Troubleshoot Dependency Issues
&lt;/h3&gt;

&lt;p&gt;During Terraform development, you may run into resource timing errors where a resource is deployed but relies on another resource that hasn't completed provisioning yet. Maybe a disk or a storage account provisions too fast half of the time or a subnet isn't deployed before a network interface. Typically this is due to a dependency issue in the configuration and is usually solved using interpolation between the proper resources or using a "depends on" block. However, these can be difficult to track down. With &lt;code&gt;terraform graph&lt;/code&gt;, you can run this command against a configuration directory, and it will produce a DOT format output. You can then copy and paste the output into a website like &lt;a href="http://www.webgraphviz.com/"&gt;WebGraphViz&lt;/a&gt; to generate a visual representation of the configuration dependencies to help troubleshoot. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use the Terraform Registry
&lt;/h3&gt;

&lt;p&gt;Especially when first starting out, don't try to reinvent the wheel. The &lt;a href="https://cloud.google.com/devops/state-of-devops"&gt;State of the DevOps report&lt;/a&gt; shows that highly efficient teams re-use other people's code. There are many Azure modules already created on the &lt;a href="https://registry.terraform.io/"&gt;Terraform Registry&lt;/a&gt;. If you need to deploy a specific Azure service, take the time to search the registry and see if a module has already been created for the service you need. If the modules that are in the Terraform registry don't meet your needs, you can fork these modules and customize them to your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When getting started with Terraform, don't try to do everything all at once. Start small and try to make minor improvements to your infrastructure little by little. Only focus on making one quality change at a time, instead of building one big massive project from the start with pipelines, modules, tests, and remote state storage.  In the end, you will achieve faster results and create a higher quality design overall.&lt;/p&gt;

&lt;p&gt;Also, keep in mind that every environment is different. Not all of these tips will fit every Terraform use case. For example, if your environment is very simple and extremely small, it may not be worth it to split up the Terraform state files. Having good judgment and design for your infrastructure code comes into play. Learn the different concepts in the community and explore how other people are using Terraform, and then do what works best for your environment. &lt;/p&gt;

&lt;p&gt;Infrastructure as code has not yet reached its maturity and has yet to become the standard way of operating for most companies.  Over the years, research has shown that companies adopting infrastructure as code are functioning at significantly higher speeds than those that are still running on traditional methods. This research is making skillsets with tools like Terraform high in demand for companies. Taking the time to learn it is well worth it. Terraform is still in its infancy stage, and the game will continue to evolve and always get better each year. Enjoy the creativity and embrace the complexity and learning that comes with infrastructure development.  &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Testing</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Sun, 16 Aug 2020 19:41:30 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-testing-1a16</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-testing-1a16</guid>
      <description>&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Module Repo Folder Structure&lt;/li&gt;
&lt;li&gt;Step 2 — Types of Testing&lt;/li&gt;
&lt;li&gt;Step 3 — Static Analysis&lt;/li&gt;
&lt;li&gt;Step 4— Unit Testing&lt;/li&gt;
&lt;li&gt;Step 5 — Integration Testing&lt;/li&gt;
&lt;li&gt;Step 6 — End-to-End Testing&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In software development, testing code is a common practice. Software developers need to be able to validate parts of their code to ensure it is working in an automated fashion. The realm of infrastructure development takes software best practices to create and manage infrastructure code, and with that comes testing. You should be writing tests for your Terraform code.&lt;/p&gt;

&lt;p&gt;Writing tests for Terraform provides many benefits that make life easier as an infrastructure developer. Automated tests provide faster loop feedback. No more making a change to a Terraform configuration and manually running &lt;code&gt;terraform apply&lt;/code&gt; and then checking the Azure portal to ensure that the change is there. Instead, we can use tools like Terratest to perform these steps for us and allow us to test our modules much faster than we could manually. Not only do we get faster feedback, but also fewer bugs. Automating tests for every possible scenario in a Terraform configuration provides better code coverage and can catch bugs much quicker. Tests give us increased confidence in our changes and provide us with greater predictability for our change. We can now accurately predict that our code deploys what we designed it to deploy without destroying other resources.&lt;/p&gt;

&lt;p&gt;One common misconception is that because Terraform is declarative, we don't need to write tests for it. We are already declaring the resource that needs to exist. If there is an issue with provisioning that resource, Terraform automatically provides the error. This is a valid point, which is why when we talk about testing our Terraform code, we want to write tests for sanity checks, conditional logic, and variable outcomes in our code. For example, writing a test for the following code to create a resource group would add minimal benefit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
    name     = "rg-myrg"
    location = var.location
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we wrote in a check to ensure that the resource group deploys to the proper location, this would be a redundant check.  The resource group will automatically be built in the location we specify, and if it's not successful, an error event occurs natively through Terraform. Writing hundreds of tests in this way could become more of a maintenance burden since we would have to continually modify the test to keep up with changes to the module.&lt;/p&gt;

&lt;p&gt;On the other hand, writing a test for the example below would be more beneficial since it contains more variance in the outcome and could potentially become altered when changes are made to the module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
    name     = "rg-myrg"
    location = var.environment != "Production" ? "southcentralus" : "northcentralus"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this guide, we are going to clone a module repository and walk through writing and running tests for it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we will be writing tests in GO, we will need an environment set up to write these tests. We are going to use Visual Studio Codespaces to walk through this guide. Visual Studio Codespaces is an online version of Visual Studio Code. It allows us to automatically deploy an environment with a code repository so we can dive into creating tests with minimal setup.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We will be using the basic sized environment for our Visual Studio Codespace. The compute for this service will be hosted in your Azure tenant, so there will be some charges associated. The average cost for a basic environment is around $0.08 per hour. Also, environments can be in a "suspended" and "active" state, which reduces pricing even further. For more information on VSC billing, check out the &lt;a href="https://azure.microsoft.com/en-us/pricing/details/visual-studio-online/"&gt;pricing page&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To create the Codespaces environment, navigate to the &lt;a href="https://online.visualstudio.com/login"&gt;Visual Studio Codespaces&lt;/a&gt; login page and sign in with your Azure credentials. Make sure to use Google Chrome as FireFox and Edge are not yet supported. Once logged in, we will be presented with the following page below. Select &lt;strong&gt;Create Codespace&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s8XoFekf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e8iflkiicmiu4zzt7jav.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s8XoFekf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e8iflkiicmiu4zzt7jav.PNG" alt="NoCodespace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to create a billing plan. The billing plan connects the Visual Studio Codespace environment to our Azure subscription. Select a location that makes sense for you. Also, you can input a plan name according to your Azure naming standards. The plan name is the name of the Azure resource that deploys to your subscription. Next, we need to specify the resource group to host the Visual Studio Codespace resource. When done configuring these settings select &lt;strong&gt;Create&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uV2L78z_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5yxpl427jsoikmzh6z85.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uV2L78z_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5yxpl427jsoikmzh6z85.PNG" alt="CreateBillingPlan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can set up our Codespace; this is the Visual Studio environment. We could have multiple Codespaces in our plan if we wanted to. Input a &lt;strong&gt;Codespace Name&lt;/strong&gt; for the Codespace. Under &lt;strong&gt;Git Repository&lt;/strong&gt; paste in the following GitHub repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/allanore/terraform-azure-testing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The GitHub repo contains the code for the Terraform module that we will create tests for in this guide. There is also a &lt;a href="https://github.com/allanore/terraform-azure-testing/blob/master/.devcontainer/post-create.sh"&gt;post-create.sh&lt;/a&gt; script in this repo that automatically installs the tools we want for our environment, like Go and Terraform. Under &lt;strong&gt;Instance Type&lt;/strong&gt;, select the Basic type. Next, select &lt;strong&gt;Create&lt;/strong&gt; to build out our VSC environment finally:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GPZ4UYOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ottbywwyl6uqcxwb5tal.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GPZ4UYOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ottbywwyl6uqcxwb5tal.PNG" alt="CreateCodespace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should see our environment starting to build and the post-create.sh script automatically executes and installs our tools and extensions. We are installing GO, Terraform, and a few other tools:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You may need to refresh your browser at some point to get this screen to show up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b8bgYzsj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/76av2taorsppxkbtklgl.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b8bgYzsj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/76av2taorsppxkbtklgl.PNG" alt="BuildingCodespace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the &lt;strong&gt;Configure Codespace&lt;/strong&gt; section shows complete, we are ready to move on to  reviewing the folder structure of this repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0bY_2JO4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x3kifirlyxj95cyndv2k.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0bY_2JO4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x3kifirlyxj95cyndv2k.PNG" alt="ConfigureCodespace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Module Repo Folder Structure &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before we can write our tests, we need to know a little more about what this module does and the structure of the repo. The function of this Terraform module is to deploy a virtual network. There are also submodules for creating a network security group and virtual network peer. In Visual Studio Codespaces, you should see the following folder structure on the left-hand side:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--medDuIOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o19m7874eb4m8sgy4g1i.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--medDuIOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o19m7874eb4m8sgy4g1i.PNG" alt="RepoStructure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the root of this repo we can see several files, we have our &lt;code&gt;.gitignore&lt;/code&gt; file, which contains a list of file types that we don't want to save into source control, like .tfstate. Next, we have our &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file, which contains a list of tools to execute on each git commit. We will go deeper into this in a later step. Last, we have our Terraform configuration files &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;output.tf&lt;/code&gt;, and &lt;code&gt;variables.tf&lt;/code&gt;. These are the root Terraform files and perform the base function of our module, which is to create a virtual network and subnet.  &lt;/p&gt;

&lt;p&gt;Now that we've gone over the files in the root folder of this module, let's go over each folder so we have a better understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.devcontainer&lt;/strong&gt; - This folder is only used to automate the Visual Studio Codespace environment. It does not affect our terraform module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.azuredevops&lt;/strong&gt; - Contains our CI pipeline for Azure DevOps. We won't be going over Azure DevOps in this guide, but if you wanted to take this guide a step further and add this repository into an Azure DevOps pipeline, this is the yml file with all the steps needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;examples&lt;/strong&gt; - The examples folder serves two purposes. First, it serves as documentation on how to use the module. Second, it serves as Terraform code that we can use for testing. We can execute these examples in our test files to stand up resources that we can test against. There are two subfolders in the examples folder. Each one has code for performing different tasks of the module. The &lt;em&gt;network&lt;/em&gt; folder contains code for standing up a Virtual Network and Network Security Group. The &lt;em&gt;vnet-peering&lt;/em&gt; folder contains code for standing up two virtual networks and then peering them together. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;modules&lt;/strong&gt; - The modules folder contains our sub-modules or &lt;em&gt;helper modules&lt;/em&gt; that provide additional features like creating a Network Security Group or Virtual Network Peer. These are optional modules that provide more flexibility and options for those that are using the module. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;test&lt;/strong&gt; - This is where our test files will live.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is best practice in Terraform to split up tasks or services into modules. We want to use our modules as building blocks to build infrastructure one piece at a time. From a developer perspective, one might think of modules like functions. Where each module is performing the heavy lifting of a specific task:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X21WUKAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y00h5vmnjupdnlfl6cze.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X21WUKAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y00h5vmnjupdnlfl6cze.PNG" alt="UsingModules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having our infrastructure built in smaller units like modules allows us to write tests for them. You can't write a test for a Terraform configuration that is thousands of lines of code. This also provides us with a smaller blast radius when making changes to code. If I make a change to my web app module, I don't put the entire application at risk.&lt;/p&gt;

&lt;p&gt;Even though there is a smaller blast radius, there is still potential to destroy infrastructure when making changes to modules, which is why there needs to be a thorough automated way to test our modules and test them often. Next, we will go over the different types of tests we can write.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2 — Types of Testing &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There are four basic types of testing. These terms are loosely categorized in the infrastructure as code world. We are still in the infant stages, and there hasn't been a standardized definition for each category. We will be going over the way that &lt;a href="https://terratest.gruntwork.io/docs/testing-best-practices/unit-integration-end-to-end-test/"&gt;Gruntwork defines testing in Terraform&lt;/a&gt;. Gruntwork is a company that provides Terraform modules to companies that desire to have IaC in their environment but don't have the skillset or time to create their own. They have years of experience developing infrastructure in Terraform and are also the creators of Terratest, which is the tool we will be using for some of our tests.  &lt;/p&gt;

&lt;p&gt;These are the four basic types of tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static Analysis&lt;/strong&gt; - Testing code without running it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Testing&lt;/strong&gt; - Testing a single unit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Testing&lt;/strong&gt; - Testing the functionality between two or more units.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-End Testing&lt;/strong&gt; - Testing an entire application infrastructure from the ground up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is a diagram demonstrating the cost of each test. We want to run most of our tests at the bottom because that is the quickest and least costly to run. Each test will also catch different types of bugs. If we aim to catch most bugs with static analysis and unit tests, we will gain much more speed in development than if we are testing for the same bug in an end-to-end test. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AaktyE2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6pnrfxvadr8rk634e5bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AaktyE2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6pnrfxvadr8rk634e5bs.png" alt="Testing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 — Static Analysis &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Static analysis tests involve testing our code without running it. We want to run a tool that can look at our code and analyze if there is a bug in it. A great way to perform static analysis testing is by using a tool called pre-commit. Pre-commit packages git hooks together to allow for tools to be run after each commit. If we look at the contents of the &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file in our repo, we can see it's configured to run several hooks. We can also see the repository URL, which contains all the git hook scripts for these hooks. This allows us to manage our git hooks and distribute them amongst team members in a much more manageable fashion. This pre-commit repo is from Gruntwork, they've done all the work to create the hooks and bundle them up into a pre-commit repository for community use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repos:
  - repo: https://github.com/gruntwork-io/pre-commit
    rev: v0.1.4
    hooks:
      - id: terraform-fmt
      - id: terraform-validate
      - id: gofmt
      - id: golint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;.precommit-config.yaml&lt;/code&gt;, there are two hooks for Terraform and two for GO. &lt;strong&gt;Terraform-fmt&lt;/strong&gt;  is a hook that runs the &lt;code&gt;terraform fmt&lt;/code&gt; command, which automatically formats code to look pretty with proper spacing. &lt;strong&gt;Terraform-validate&lt;/strong&gt; runs the &lt;code&gt;terraform validate&lt;/code&gt; command against our code to ensure it is syntactically correct by checking for no misplaced &lt;code&gt;{ }&lt;/code&gt; or invalid Terraform syntax.  &lt;/p&gt;

&lt;p&gt;Let's give these two hooks a test by modifying some Terraform code. Open the &lt;code&gt;variables.tf&lt;/code&gt; file in the root of the repo and add a description to the system variable. Let's purposely use the argument &lt;code&gt;descriptions&lt;/code&gt; instead of &lt;code&gt;description&lt;/code&gt; in our variable to catch the error in the pre-commit hook. Copy the snippet below and overwrite the content currently there for the &lt;code&gt;system&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "system" {
  type         = string
  descriptions = "Name of the system or environment"
  default      = "terratest"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Save the changes by entering &lt;strong&gt;CTRL + S&lt;/strong&gt; on the keyboard. Before pre-commit can run, we need to configure the hooks for this repository. Open up the terminal by entering &lt;strong&gt;CTRL + ~&lt;/strong&gt; on the keyboard and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pre-commit install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let's try to commit our change we made to the system variable in the &lt;code&gt;variables.tf&lt;/code&gt; file. When we run our commit, the pre-commit hooks for &lt;code&gt;terraform fmt&lt;/code&gt; and &lt;code&gt;terraform validate&lt;/code&gt; are triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m "this is a test commit to trigger our pre-commit hooks"

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We should see in the output that &lt;code&gt;terraform validate&lt;/code&gt; has run and failed. We should also see the error message describing why it failed, which is because we used the argument &lt;code&gt;descriptions&lt;/code&gt; instead of &lt;code&gt;description&lt;/code&gt; which is the correct argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Terraform has installed the required providers to support the configuration
upgrade process. To begin upgrading your configuration, run the following:
    terraform 0.12upgrade

To see the full set of errors that led to this message, run:
    terraform validate

Error: Unsupported argument

  on variables.tf line 3, in variable "system":
   3:   descriptions = "Name of the system or environment"

An argument named "descriptions" is not expected here. Did you mean
"description"?

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To fix the change, rename the&lt;code&gt;descriptions&lt;/code&gt; argument to &lt;code&gt;description&lt;/code&gt;, which should clear our invalid syntax error. But this time, add a large number of spaces in the &lt;code&gt;default&lt;/code&gt; argument. This unsightly formatting will trigger our &lt;code&gt;terraform fmt&lt;/code&gt; pre-commit hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "system" {
  type         = string
  description = "Name of the system or environment"
  default      =                        "terratest"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, let's add and commit the changes again in git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m "this is a test commit to trigger our pre-commit hooks"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This time we see that &lt;code&gt;terraform fmt&lt;/code&gt; failed. We added that additional spaces in there which goofed up the formatting in our code. &lt;code&gt;Terraform fmt&lt;/code&gt; automatically runs and corrects these changes when the pre-commit hook runs, if it detects any formatting changes made, it will cause a failure like so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;gofmt&lt;/code&gt; and &lt;code&gt;golint&lt;/code&gt; hooks display as skipped. This is because we did not modify any GO files. The pre-commit hooks only run against the files that have been modified and will only trigger against each one's respective file type.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform fmt............................................................Failed
- hook id: terraform-fmt
- files were modified by this hook

variables.tf

Terraform validate.......................................................Passed
gofmt................................................(no files to check)Skipped
golint...............................................(no files to check)Skipped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we look at our &lt;code&gt;variables.tf&lt;/code&gt; file, we can see that &lt;code&gt;terraform fmt&lt;/code&gt; has corrected our spaces. Now we can run our add and commit again to save all our changes finally. In the output we can see that both checks pass this time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vsonline:~/workspace/terraform-azure-testing$ git add .
vsonline:~/workspace/terraform-azure-testing$ git commit -m "this is a test commit to trigger our pre-commit hooks"
Terraform fmt............................................................Passed
Terraform validate.......................................................Passed
gofmt................................................(no files to check)Skipped
golint...............................................(no files to check)Skipped
[master 9b67894] this is a test commit to trigger our pre-commit hooks
 1 file changed, 3 insertions(+), 2 deletions(-)

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now our commit is complete. With pre-commit hooks, we can catch bugs before they are committed to code. This process makes our commits much cleaner and prevents silly mistakes from getting committed to source control. It also helps out with code reviews by weeding out syntactical mistakes. We can catch errors quicker in development, where if we just made a change and ran Terraform apply to test, we would have to wait for that process to kick-off before we realized our configuration wasn't syntactically correct. We can iterate through our code quicker, which speeds up development. We could also perform static analysis testing within a CI/CD pipeline to analyze our Terraform plan.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Terraform validate&lt;/code&gt; is a basic static analysis test that we can perform. There are many other static analysis tools in the community that provide great benefits. Below are a few to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/terraform-linters/tflint"&gt;TFlint&lt;/a&gt; - Catch provider specific bugs like incorrect Azure VM sizes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/instrumenta/conftest"&gt;ConfTest&lt;/a&gt; - Analyze TF plan files and enforces defined policies and governance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/bridgecrewio/checkov"&gt;CheckOV&lt;/a&gt; - Security and compliance on the Terraform configuration level. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4— Unit Testing &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A unit test involves testing a single unit or individual component. This is the smallest testable piece of code that can be achieved. In Terraform, we could write tests that check the resource blocks in our Terraform configuration, however many in the Terraform community agree that it offers very little value. To write a valuable unit test, we need to communicate with the provider and stand up infrastructure, which by software development testing standards is technically not a true unit test. Its an integration test. In infrastructure development with Terraform, Gruntwork defines a unit test as testing a single module by itself. &lt;/p&gt;

&lt;p&gt;We are going to write a unit test for our networking module. We will create a test that uses Terratest. Terratest is a GO library, built by Gruntwork, for testing Terraform. Our unit test will automatically deploy the Terraform code from the &lt;code&gt;examples/network&lt;/code&gt; folder, test to ensure the desired outcome is achieved in Azure using the Azure API, then run a &lt;code&gt;terraform destroy&lt;/code&gt; and tear down our test infrastructure at the end. The process is the same as if we were manually testing the module, except we are automating it.&lt;/p&gt;

&lt;p&gt;First, we need to create a  GO test file called &lt;code&gt;terraform_azure_network_test.go&lt;/code&gt;. To make this file, right-click on the &lt;code&gt;test&lt;/code&gt; folder in Visual Studio Codespace and select &lt;strong&gt;new file&lt;/strong&gt;. Then name the file &lt;code&gt;terraform_azure_network_test.go&lt;/code&gt;. Notice that file ends in &lt;code&gt;_test.go&lt;/code&gt;. This format indicates that the file is a GO test file. Go will automatically see this file as a test file and execute it when we run our test command later. Now we will create the foundational code for our unit test. &lt;/p&gt;

&lt;h5&gt;
  
  
  Base Setup
&lt;/h5&gt;

&lt;p&gt;Below is the code for a base test setup. I typically use this as a starting point for creating a test and then expand from there. Copy this code to the &lt;code&gt;terraform_azure_network_test.go&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (
  "github.com/gruntwork-io/terratest/modules/terraform"
  "testing"
)


func TestTerraformAzureNetworkingExample(t *testing.T) {
    t.Parallel()

    terraformOptions := &amp;amp;terraform.Options{
    }

    // At the end of the test, run `terraform destroy` to clean up any resources that were created
    defer terraform.Destroy(t, terraformOptions)

    // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
    terraform.InitAndApply(t, terraformOptions)

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's review all the parts and pieces of the code. Starting from the top we have &lt;code&gt;package test&lt;/code&gt; declaring that the name of our package is test (which describes the purpose of our package):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we have our &lt;code&gt;import&lt;/code&gt; declaration. The &lt;code&gt;import&lt;/code&gt; declaration contains libraries or packages used in the Golang code. In Golang, libraries and packages are similar to modules in PowerShell. We can reference packages native to GO, or we can reference ones from source control repos like GitHub. We will be adding more libraries as we build out our unit test, for now, we are using the &lt;code&gt;testing&lt;/code&gt; GO package and the &lt;code&gt;github.com/gruntwork-io/terratest/modules/terraform&lt;/code&gt; library created by Gruntwork, which allows us to automate deploying code with Terraform during the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
  "github.com/gruntwork-io/terratest/modules/terraform"
  "testing"
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After our &lt;code&gt;import&lt;/code&gt; declaration, we have our testing function, which we called &lt;code&gt;TestTerraformAzureNetworkingExample&lt;/code&gt;. Testing functions in GO have the following format; they also need to start with the word &lt;code&gt;Test&lt;/code&gt; with a capital &lt;code&gt;T&lt;/code&gt; followed by a capitalized letter after &lt;code&gt;Test&lt;/code&gt;. We also are using &lt;code&gt;t.Parallel()&lt;/code&gt; which indicates that we want to run this test in parallel with other tests. This parallel statement is a big deal because we can run multiple tests at once to test our module in several different ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func TestTerraformAzureNetworkingExample(t *testing.T) {
    t.Parallel()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Inside our function is a variable called &lt;code&gt;terraformOptions&lt;/code&gt;. In GO, we can declare variables by using &lt;code&gt;:=&lt;/code&gt;, which is different than in PowerShell, where we would use &lt;em&gt;$variables = somevalue&lt;/em&gt; to declare a variable. Inside our variable declaration, we are using the &lt;code&gt;github.com/gruntwork-io/terratest/modules/terraform&lt;/code&gt; library by referencing the package name of our library &lt;code&gt;terraform&lt;/code&gt;. Then we are referencing the object inside the library called &lt;code&gt;Options&lt;/code&gt;. This object is called a &lt;em&gt;struct&lt;/em&gt; in Golang. A &lt;em&gt;struct&lt;/em&gt; is similar to an object in PowerShell. We are essentially making an options object that we can add a collection of settings to and pass them through to Terraform. For now, we have no options defined in our struct, but in the next step, we will add them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    terraformOptions := &amp;amp;terraform.Options{
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To finish out our testing function, we have two more lines of code. We are using the Terratest library again by referencing &lt;code&gt;terraform&lt;/code&gt;. However, this time we are using the &lt;code&gt;Destroy&lt;/code&gt; and &lt;code&gt;InitAndApply&lt;/code&gt; functions from the &lt;a href="https://github.com/gruntwork-io/terratest/tree/master/modules/terraform"&gt;Terratest library&lt;/a&gt; to perform the standard terraform init, apply, and destroy commands using our defined options in the &lt;code&gt;terraformOptions&lt;/code&gt; variables. Note, the &lt;code&gt;defer&lt;/code&gt; in GO is similar to a &lt;em&gt;finally&lt;/em&gt; in PowerShell. The defer means GO will run this function at the end and will always run it even if the test were to error out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defer terraform.Destroy(t, terraformOptions)

terraform.InitAndApply(t, terraformOptions)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Configure Terraform Options
&lt;/h5&gt;

&lt;p&gt;Let's add some options to our &lt;code&gt;terraformOptions&lt;/code&gt; variable. Our Terraform code in the &lt;code&gt;examples/network&lt;/code&gt; folder has a &lt;code&gt;variables.tf&lt;/code&gt; file that allows us to input several parameters and customize our network infrastructure when deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "system" {
  type        = string
  description = "Name of the system or environment"
  default     = "terratest"
}
variable "location" {
  type        = string
  description = "Azure location of terraform server environment"
  default     = "westus2"
}
variable "vnet_address_space" {
  description = "Address space for Virtual Network"
}

variable "subnet_prefix" {
  type = string
  description = "Prefix of subnet address"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We need to define the values for these variables in our unit test and then pass them through to Terraform using the &lt;code&gt;terraformOptions&lt;/code&gt; variable. &lt;/p&gt;

&lt;p&gt;If you notice in our &lt;code&gt;variables.tf&lt;/code&gt; file, we have a variable for &lt;code&gt;location&lt;/code&gt;. For the &lt;code&gt;location&lt;/code&gt; variable value, we want to be able to randomly test deploying into different regions that could be a possibility for us to use. This randomization allows us to have a thorough test since each test can deploy infrastructure into one of the listed regions. We will do this by providing a list of possible regions to deploy to in our test and then randomly pick one when the test runs. Doing this can also help sniff out any region-specific incidents occurring in Azure when we run our tests.  &lt;/p&gt;

&lt;p&gt;To create a list of regions, we are declaring a variable called &lt;code&gt;regions&lt;/code&gt; and setting the variable type to &lt;code&gt;[]string&lt;/code&gt; which stands for a list of strings. Then, we are filling out that list with various regions in Azure that we want to test in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    var regions = []string{
        "centralus",
        "eastus",
        "eastus2",
        "northcentralus",
        "southcentralus",
        "westcentralus",
        "westus",
        "westus2",
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we create a variable for our &lt;code&gt;regions&lt;/code&gt; that uses the function &lt;code&gt;RandomString&lt;/code&gt; to pick a region from our list of regions randomly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azureRegion := random.RandomString(regions)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have a variable created for the Terraform location input variable, we can configure the values for the rest of the input variables. For the &lt;code&gt;system&lt;/code&gt; variable, we are going to use a similar concept that we used for the Azure region. We are going to generate a random name for the system name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemName := strings.ToLower(random.UniqueId())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This random name generation allows us to run this test several times at the same time without overlapping within the same resource name. It is useful when multiple people are adding features to a module on their branches and are running the tests. Next, we have hardcoded values for our virtual network address and subnet prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vnetAddress := "10.0.0.0/16"
subnetPrefix := "10.0.0.0/24"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have values for our input variables, we need to pass them into &lt;code&gt;terraformOptions&lt;/code&gt;. We add the &lt;code&gt;TerraformDir&lt;/code&gt; argument to specify the directory of the code we want to execute is the code in the &lt;code&gt;examples/network&lt;/code&gt; folder of our repo. Next, we are importing a map of variables, which allows us to pass values through to Terraform as if we were using the &lt;code&gt;-var&lt;/code&gt; option in the command line. We are passing through all four variables that we just set up in GO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraformOptions := &amp;amp;terraform.Options{

        TerraformDir: "../examples/network",

        Vars: map[string]interface{}{
            "system":             systemName,
            "location":           azureRegion,
            "vnet_address_space": vnetAddress,
            "subnet_prefix":      subnetPrefix,
        },
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We need to add two more packages and libraries to our &lt;code&gt;import&lt;/code&gt; declaration since we are now using them to create a random string for the system name and to choose our Azure region randomly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
    "strings"
    "testing"


    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we have our test configured to run our Terraform code in the &lt;code&gt;examples/network&lt;/code&gt; folder with values for the &lt;code&gt;variables.tf&lt;/code&gt; file. At this point, our unit test in &lt;code&gt;terraform_azure_network_test.go&lt;/code&gt; should look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (
    "strings"
    "testing"

    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
)

// An example of how to test the Terraform module in examples/terraform-azure-example using Terratest.
func TestTerraformAzureNetworkingExample(t *testing.T) {
    t.Parallel()

    var regions = []string{
        "centralus",
        "eastus",
        "eastus2",
        "northcentralus",
        "southcentralus",
        "westcentralus",
        "westus",
        "westus2",
    }

    // Pick a random Azure region to test in.
    azureRegion := random.RandomString(regions)

    // Network Settings for Vnet and Subnet
    systemName := strings.ToLower(random.UniqueId())
    vnetAddress := "10.0.0.0/16"
    subnetPrefix := "10.0.0.0/24"

    terraformOptions := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/network",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             systemName,
            "location":           azureRegion,
            "vnet_address_space": vnetAddress,
            "subnet_prefix":      subnetPrefix,
        },
    }

    // At the end of the test, run `terraform destroy` to clean up any resources that were created
    defer terraform.Destroy(t, terraformOptions)

    // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
    terraform.InitAndApply(t, terraformOptions)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Add In The Tests
&lt;/h5&gt;

&lt;p&gt;Now we are ready to add in some tests. We want to write tests that are meaningful and don't want the process to become a burden to maintain. Determining if a test is meaningful depends on the situation and environment. A meaningful test for someone else doesn't mean it is meaningful for your case. For this example, we are going to keep things simple. We want to test that our virtual network subnet is truly associated with an NSG after our Terraform example is deployed. This test ensures that any change to our module in the future won't interrupt this desired outcome, which could result in a potential security risk if the NSG was not assigned to the subnet.&lt;/p&gt;

&lt;p&gt;Our Terraform code in the &lt;code&gt;examples/network&lt;/code&gt; folder contains the following in the &lt;code&gt;output.tf&lt;/code&gt; file. These are values we want to use to perform our tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "vnet_rg" {
  description = "Location of vnet"
  value       = module.vnet.vnet_rg
}

output "subnet_id" {
  description = "Subnet ID"
  value       = module.vnet.subnet_id
}

output "nsg_name" {
  description = "Name of vnet Security Group"
  value       = module.nsg.nsg_name
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We want to make sure our modules are not too large to be tested. We don't want a 10,000 line module because it's not possible to unit test. The more you write tests for your modules, the more structured your modules become for testing. This practice becomes a great benefit as it allows for a more stable and better structured Terraform code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;terraform.Output&lt;/code&gt; function allows us to collect output values from our Terraform deployment after the &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; has completed. We are referencing the &lt;code&gt;terraformOptions&lt;/code&gt; variable which contains our Terraform environment settings. Also we include each output value from &lt;code&gt;output.tf&lt;/code&gt;. Then we save each output value into a variable in GO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vnetRG := terraform.Output(t, terraformOptions, "vnet_rg")
subnetID := terraform.Output(t, terraformOptions, "subnet_id")
nsgName := terraform.Output(t, terraformOptions, "nsg_name")
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we will use the output variables we created to directly check our Azure environment using the Azure API and confirm that the NSG we create is assigned to the subnet. We perform a lookup of all subnet IDs assigned to the NSG using the &lt;code&gt;azure.GetAssociationsforNSG&lt;/code&gt; function, which requires the virtual network resource group and NSG name. Then we run a test using the &lt;code&gt;assert&lt;/code&gt; GO package, which allows us to compare all the resources IDs in our &lt;code&gt;nsgAssociations&lt;/code&gt; variable with the &lt;code&gt;subnetID&lt;/code&gt; variable, which contains the output information from our Terraform code. This comparison allows us to take the subnet ID output from Terraform and look into Azure using the API and verify that it's assigned to the NSG we created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Look up Subnet and NIC ID associations of NSG
    nsgAssociations := azure.GetAssociationsforNSG(t, vnetRG, nsgName, "")

    //Check if subnet is associated with NSG
    assert.Contains(t, nsgAssociations, subnetID)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If the subnet ID is in the list of NSG associations, our test will pass, if it is missing, the test will fail. &lt;/p&gt;

&lt;p&gt;Lastly, we will need to add two more libraries to our &lt;code&gt;import&lt;/code&gt; declaration. We are using &lt;code&gt;assert&lt;/code&gt; to perform a comparison and validate our subnet ID is associated with the NSG, and we are using the function &lt;code&gt;azure.GetAssociationsforNSG&lt;/code&gt; from the GO library aztest. The &lt;code&gt;azure.GetAssociationsforNSG&lt;/code&gt; function allows us to quickly authenticate with our Azure environment and look up assigned resources to an NSG. It is just a helper function for working with the &lt;a href="https://github.com/Azure/azure-sdk-for-go"&gt;Azure GO SDK&lt;/a&gt; which communicates with the Azure API:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;a href="https://godoc.org/github.com/gruntwork-io/terratest/modules/azure"&gt;Azure test library&lt;/a&gt; in Terratest right now is fairly limited compared to &lt;a href="https://godoc.org/github.com/gruntwork-io/terratest/modules/aws"&gt;AWS&lt;/a&gt;. Gruntwork has been heavily focused on building out the AWS and GCP modules. In the current state, most companies are just making their own private libraries for testing their resources in Azure. To help people get started writing tests for Terraform code in Azure, I have expanded upon the Terratest Azure testing functions and created a public GO library called &lt;a href="https://github.com/allanore/aztest"&gt;AzTest&lt;/a&gt;, feel free to use it.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
    "strings"
    "testing"

    "github.com/allanore/aztest/modules/azure"
    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have our test written, our entire unit test is as follows. Copy the following to &lt;code&gt;terraform_azure_network_test.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (
    "strings"
    "testing"

    "github.com/allanore/aztest/modules/azure"
    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

// An example of how to test the Terraform module in examples/terraform-azure-example using Terratest.
func TestTerraformAzureNetworkingExample(t *testing.T) {
    t.Parallel()

    var regions = []string{
        "centralus",
        "eastus",
        "eastus2",
        "northcentralus",
        "southcentralus",
        "westcentralus",
        "westus",
        "westus2",
    }

    // Pick a random Azure region to test in.
    azureRegion := random.RandomString(regions)

    // Network Settings for Vnet and Subnet
    systemName := strings.ToLower(random.UniqueId())
    vnetAddress := "10.0.0.0/16"
    subnetPrefix := "10.0.0.0/24"

    terraformOptions := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/network",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             systemName,
            "location":           azureRegion,
            "vnet_address_space": vnetAddress,
            "subnet_prefix":      subnetPrefix,
        },
    }

    // At the end of the test, run `terraform destroy` to clean up any resources that were created
    defer terraform.Destroy(t, terraformOptions)

    // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
    terraform.InitAndApply(t, terraformOptions)

    // Run `terraform output` to get the value of an output variable
    vnetRG := terraform.Output(t, terraformOptions, "vnet_rg")
    subnetID := terraform.Output(t, terraformOptions, "subnet_id")
    nsgName := terraform.Output(t, terraformOptions, "nsg_name")

    // Look up Subnet and NIC ID associations of NSG
    nsgAssociations := azure.GetAssociationsforNSG(t, vnetRG, nsgName, "")

    //Check if subnet is associated with NSG
    assert.Contains(t, nsgAssociations, subnetID)

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Whew! We just built our first unit test in GO. Now, let's run it. First, we need to authenticate to Azure. The easiest way is to use Azure CLI. Type in the following command in the terminal while in the &lt;code&gt;~/workspace/terraform-azure-testing/test&lt;/code&gt; directory. Log in with your Azure account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, we need to set the &lt;code&gt;ARM_SUBSCRIPTION_ID&lt;/code&gt; environment variable which is used for several functions in Terratest when deploying to Azure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ARM_SUBSCRIPTION_ID=$(az account show | jq '.id' -r)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once logged in with Azure CLI, we need to download all of our dependencies for our tests. The dependencies include the packages that we specified during the import declaration. This concept is similar to running &lt;em&gt;install-module&lt;/em&gt; in PowerShell to install all the external modules used in a script.  Type the following command using &lt;code&gt;go get&lt;/code&gt; while in the &lt;code&gt;test&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go get -t -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We see the libraries and packages download. This process may take a minute. Once complete, we are ready to run our test. To kick off the test, type in the following command. We are using the &lt;code&gt;go test&lt;/code&gt; command with &lt;code&gt;-v&lt;/code&gt; to specify verbose output. Also, we are specifying the GO test file that we just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go test -v terraform_azure_network_test.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will start to see Terraform executing our example code in the output display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestTerraformAzureNetworkingExample
=== PAUSE TestTerraformAzureNetworkingExample
=== CONT  TestTerraformAzureNetworkingExample
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z retry.go:72: terraform [init -upgrade=false]
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:86: Running command terraform with args [init -upgrade=false]
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:168: Initializing modules...
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:168: 
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:168: Initializing the backend...
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:168: 
TestTerraformAzureNetworkingExample 2020-05-09T14:30:17Z command.go:168: Initializing provider plugins...
TestTerraformAzureNetworkingExample 2020-05-09T14:30:19Z command.go:168: 
TestTerraformAzureNetworkingExample 2020-05-09T14:30:19Z command.go:168: Terraform has been successfully initialized!
TestTerraformAzureNetworkingExample 2020-05-09T14:30:19Z command.go:168: 

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Don't hit CTRL + C during the test; this will prevent resources from being cleaned up in Azure. If the test should error out, the defer stage runs and executes Terraform destroy to remove anything that was provisioned.&lt;/p&gt;

&lt;p&gt;The test is executing the Terraform code in the &lt;code&gt;examples/network&lt;/code&gt; folder, running a test to ensure that the NSG is associated with the subnet, and running Terraform destroy at the end. Once the test run completes, we see the result like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestTerraformAzureNetworkingExample 2020-05-09T14:32:01Z command.go:168: azurerm_resource_group.rg: Destruction complete after 47s
TestTerraformAzureNetworkingExample 2020-05-09T14:32:01Z command.go:168: 
TestTerraformAzureNetworkingExample 2020-05-09T14:32:01Z command.go:168: Destroy complete! Resources: 7 destroyed.
--- PASS: TestTerraformAzureNetworkingExample (103.78s)
PASS
ok      command-line-arguments  103.783s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now our unit test is complete! In the next step, we will add an integration test. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Integration Testing &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;An integration test with infrastructure code is defined as testing the functionality of two components interacting together. This could be a Webapp with a SQL database or two microservices interacting with each other. For demonstration purposes, we are going to keep it very simple and stand up two virtual networks and test that we can peer them together. The example code for each vnet is in the &lt;code&gt;examples/vnet-peering&lt;/code&gt; directory. Our integration test will execute the Terraform code in &lt;code&gt;vnet1&lt;/code&gt; to deploy the first virtual network. Once complete, the test will execute the code in &lt;code&gt;vnet2&lt;/code&gt; to deploy a second virtual network and peer them together. We will then write a test that validates that the peering was successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;examples
    └──networking
    └──vnet-peering
      └─vnet1
          └─main.tf
          └─output.tf
          └─variables.tf
      └─vnet2
          └─main.tf
          └─output.tf
          └─variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;test&lt;/code&gt; directory, let's create another GO test file called &lt;code&gt;terraform_azure_network_peering_test.go&lt;/code&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Base Test
&lt;/h5&gt;

&lt;p&gt;We will copy the following code below and paste it in as our base test configuration. Notice we now have two sets of &lt;code&gt;terraform.options&lt;/code&gt; as well as two sets of the destroy and apply steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (

    "testing"


    "github.com/gruntwork-io/terratest/modules/terraform"
)

// An example of how to test the Terraform module in examples/terraform-azure-example using Terratest.
func TestTerraformAzureNetworkingPeeringExample(t *testing.T) {
    t.Parallel()


    vnet1Opts := &amp;amp;terraform.Options{

    }

    // Deploy VNet1
    defer terraform.Destroy(t, vnet1Opts)
    terraform.InitAndApply(t, vnet1Opts)

    vnet2Opts := &amp;amp;terraform.Options{

    }

    // Deploy VNet2
    defer terraform.Destroy(t, vnet2Opts)
    terraform.InitAndApply(t, vnet2Opts)
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Add in Terraform Options
&lt;/h5&gt;

&lt;p&gt;Next, we will add in the Terraform options. We include the steps for randomized Azure regions as well as using random system names for our virtual networks. The big difference from our unit test is that we are passing the Terraform output values after deploying VNet1 to the &lt;code&gt;terraform.options&lt;/code&gt; of VNet2, this allows us to configure the peering between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (
    "strings"
    "testing"

    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
)

// An example of how to test the Terraform module in examples/terraform-azure-example using Terratest.
func TestTerraformAzureNetworkingPeeringExample(t *testing.T) {
    t.Parallel()

    var regions = []string{
        "centralus",
        "eastus",
        "eastus2",
        "northcentralus",
        "southcentralus",
        "westcentralus",
        "westus",
        "westus2",
    }

    // Pick a random Azure region to test in.
    azureRegion := random.RandomString(regions)

    // Network Settings for Vnet and Subnet
    vnet1Sysname := strings.ToLower(random.UniqueId())
    vnet1Address := "10.0.0.0/16"
    vnet1SubnetPrefix := "10.0.0.0/24"
    vnet2Sysname := strings.ToLower(random.UniqueId())
    vnet2Address := "10.1.0.0/16"
    vnet2SubnetPrefix := "10.1.0.0/24"

    vnet1Opts := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/vnet-peering/vnet1",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             vnet1Sysname,
            "location":           azureRegion,
            "vnet_address_space": vnet1Address,
            "subnet_prefix":      vnet1SubnetPrefix,
        },
    }

    // Deploy VNet1
    defer terraform.Destroy(t, vnet1Opts)
    terraform.InitAndApply(t, vnet1Opts)
    vnetOutRG := terraform.Output(t, vnet1Opts, "vnet_rg")
    vnetOutName := terraform.Output(t, vnet1Opts, "vnet_name")

    vnet2Opts := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/vnet-peering/vnet2",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             vnet2Sysname,
            "location":           azureRegion,
            "vnet_address_space": vnet2Address,
            "subnet_prefix":      vnet2SubnetPrefix,
            "peer_vnet_rg":       vnetOutRG,
            "peer_vnet_name":     vnetOutName,
        },
    }

    // Deploy VNet2
    defer terraform.Destroy(t, vnet2Opts)
    terraform.InitAndApply(t, vnet2Opts)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Add In The tests
&lt;/h5&gt;

&lt;p&gt;Next, we will add in the tests. We are collecting the resource group and virtual network name from the output of deploying VNet2. Then we are using those values to look up the virtual network with the &lt;code&gt;azure.GetVnetbyName&lt;/code&gt; function and storing them in the &lt;code&gt;vnet2Properties&lt;/code&gt; variable. We are taking the virtual network peering properties from the &lt;code&gt;vnet2Properties&lt;/code&gt; variable and using a &lt;code&gt;for&lt;/code&gt; loop in GO to loop through all of the values within the &lt;code&gt;VirtualNetworkPeerings&lt;/code&gt; property, which contains a list of all peerings made on the virtual network. We are then validating that all peerings in that property are in a &lt;code&gt;Succeeded&lt;/code&gt; state, ensuring that they peered properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Collect RG and Virtual Network name from VNet2 Output
    vnet2RG := terraform.Output(t, vnet2Opts, "vnet_rg")
    vnet2Name := terraform.Output(t, vnet2Opts, "vnet_name")

    // Look up Virtual Network 2 by Name
    vnet2Properties := azure.GetVnetbyName(t, vnet2RG, vnet2Name, "")

    //Check if VNet Peering in VNet2 Provisioned Successfully
    for _, vnet := range *vnet2Properties.VirtualNetworkPeerings {
        assert.Equal(t, "Succeeded", string(vnet.VirtualNetworkPeeringPropertiesFormat.ProvisioningState), "Check if Peerings provisioned successfully")
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our final integration test file should look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package test

import (
    "strings"
    "testing"

    "github.com/allanore/aztest/modules/azure"
    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

// An example of how to test the Terraform module in examples/terraform-azure-example using Terratest.
func TestTerraformAzureNetworkingPeeringExample(t *testing.T) {
    t.Parallel()

    var regions = []string{
        "centralus",
        "eastus",
        "eastus2",
        "northcentralus",
        "southcentralus",
        "westcentralus",
        "westus",
        "westus2",
    }

    // Pick a random Azure region to test in.
    azureRegion := random.RandomString(regions)

    // Network Settings for Vnet and Subnet
    vnet1Sysname := strings.ToLower(random.UniqueId())
    vnet1Address := "10.0.0.0/16"
    vnet1SubnetPrefix := "10.0.0.0/24"
    vnet2Sysname := strings.ToLower(random.UniqueId())
    vnet2Address := "10.1.0.0/16"
    vnet2SubnetPrefix := "10.1.0.0/24"

    vnet1Opts := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/vnet-peering/vnet1",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             vnet1Sysname,
            "location":           azureRegion,
            "vnet_address_space": vnet1Address,
            "subnet_prefix":      vnet1SubnetPrefix,
        },
    }

    // Deploy VNet1
    defer terraform.Destroy(t, vnet1Opts)
    terraform.InitAndApply(t, vnet1Opts)
    vnetOutRG := terraform.Output(t, vnet1Opts, "vnet_rg")
    vnetOutName := terraform.Output(t, vnet1Opts, "vnet_name")

    vnet2Opts := &amp;amp;terraform.Options{

        // The path to where our Terraform code is located
        TerraformDir: "../examples/vnet-peering/vnet2",

        // Variables to pass to our Terraform code using -var options
        Vars: map[string]interface{}{
            "system":             vnet2Sysname,
            "location":           azureRegion,
            "vnet_address_space": vnet2Address,
            "subnet_prefix":      vnet2SubnetPrefix,
            "peer_vnet_rg":       vnetOutRG,
            "peer_vnet_name":     vnetOutName,
        },
    }

    // Deploy VNet2
    defer terraform.Destroy(t, vnet2Opts)
    terraform.InitAndApply(t, vnet2Opts)

    // Collect RG and Virtual Network name from VNet2 Output
    vnet2RG := terraform.Output(t, vnet2Opts, "vnet_rg")
    vnet2Name := terraform.Output(t, vnet2Opts, "vnet_name")

    // Look up Virtual Network 2 by Name
    vnet2Properties := azure.GetVnetbyName(t, vnet2RG, vnet2Name, "")

    //Check if VNet Peering in VNet2 Provisioned Successfully
    for _, vnet := range *vnet2Properties.VirtualNetworkPeerings {
        assert.Equal(t, "Succeeded", string(vnet.VirtualNetworkPeeringPropertiesFormat.ProvisioningState), "Check if Peerings provisioned successfully")
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, let's run our integration test. No need to download the dependencies because we downloaded them already when we ran the unit test. Run the following command in the terminal specifying the &lt;code&gt;terraform_azure_network_peering_test.go&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go test -v terraform_azure_network_peering_test.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We should see both virtual networks get deployed, followed by our test to verify the peering state between them. Then a &lt;code&gt;terraform destroy&lt;/code&gt; is ran to tear down our test infrastructure. At the end we should see that our test has passed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestTerraformAzureNetworkingPeeringExample 2020-05-10T13:22:40Z command.go:168: azurerm_resource_group.rg: Destruction complete after 48s
TestTerraformAzureNetworkingPeeringExample 2020-05-10T13:22:41Z command.go:168: 
TestTerraformAzureNetworkingPeeringExample 2020-05-10T13:22:41Z command.go:168: Destroy complete! Resources: 7 destroyed.
--- PASS: TestTerraformAzureNetworkingPeeringExample (347.89s)
PASS
ok      command-line-arguments  347.890s

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For an extra challenge, we could run both tests at the same time using the following command. Because we have &lt;code&gt;t.Parallel()&lt;/code&gt; in our test functions, each test can be run in parallel. This is incredibly powerful because we can test our modules in several different ways at the same time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In a typical continuous integration pipeline for our module, we would ideally be running all tests in our test folder at the same time when we commit our changes to the module repo. This allows us to get a full amount of test coverage against our module.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go test -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the next step we go over how to perform end to end testing. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — End-to-End Testing &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;End-to-end testing involves standing up the entire application composed of multiple modules. This can take hours, depending on how big the application is, which can make it incredibly time-consuming to run end-to-end tests. If the application takes a significant amount of time to deploy, it is recommended to deploy a copy of the entire application in a separate test environment and keep it deployed for a specific amount of time. Then, when we make a change to any of the modules in the application, we can re-deploy that module out to the application and then test the application functionality as a whole with automated tests.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As you can see, writing tests can take a bit of work. Because IaC is such a newly adopted concept, the tooling and concepts for writing tests can be awkward and complex. However, the rewards are worth it. A module with thorough tests will have much more stability and provides greater confidence when teams are executing that code. &lt;/p&gt;

&lt;p&gt;Infrastructure code typically "rots" quickly. There are constantly new updates made to Azure, Terraform providers, and Terraform itself, which can make it a full-time task to ensure modules are always working as they should. To help with this, consider scheduling tests to run nightly to catch these types of bugs as early as possible. &lt;/p&gt;

&lt;p&gt;It is strongly encouraged to run your tests in an Azure subscription separate from production and even development. When developing and testing modules, there is always a risk of destroying other resources. Also, this allows for safely configuring automated cleanup on the testing subscription to ensure any tests that bombed-out arent leaving remnants of infrastructure.&lt;/p&gt;

&lt;p&gt;There is also the concept of test-driven development (TDD), which is a software development practice where tests are written first before the code is written. This is not very common in the infrastructure development world yet because of its infancy stage. However, IaC veterans like &lt;a href="https://www.thoughtworks.com/profiles/kief-morris"&gt;Kief Morris&lt;/a&gt; are huge advocates for TDD and describe that performing TDD for infrastructure code drives better design as it requires one to think through the outcome and function of their infrastructure when developing.&lt;/p&gt;

&lt;p&gt;Tests for infrastructure code are critical to the stability of the infrastructure. Jacob Kaplan-Moss, software developer and co-creator of Django said it best, "Code without tests is broken as designed". Writing tests for IaC can be time-consuming and difficult because of the immaturity of the tooling; however, it is an essential piece of infrastructure development that shouldn't be overlooked.&lt;/p&gt;

&lt;p&gt;In the next article, we will complete out this series by reviewing best practices when using Terraform. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
      <category>testing</category>
    </item>
    <item>
      <title>
Getting Started with Terraform on Azure: Importing Existing Infrastructure</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Sun, 16 Aug 2020 19:14:11 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-importing-existing-infrastructure-43fa</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-importing-existing-infrastructure-43fa</guid>
      <description>&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Simple Import&lt;/li&gt;
&lt;li&gt;Step 2 — Complex Imports&lt;/li&gt;
&lt;li&gt;Step 3 — Import Modules&lt;/li&gt;
&lt;li&gt;Step 4 — Remote State&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When first introduced to Terraform, we can see how easy it is to build new environments and manage them with software development practices. We start to experience the numerous benefits that come with infrastructure as code such as deployment speed, stability through templatized environments, and transparency through code documentation. However, all these benefits emerge from the new infrastructure we are creating with Terraform. What about our old pre-existing infrastructure? How can we manage the environments we've already built by hand with code?&lt;/p&gt;

&lt;p&gt;One of the main principles with infrastructure as code is to "define everything in code". If this principle only applies to new environments, we are greatly diminishing the benefits gained by limiting this process to only a small scope of the environment. This is why it's essential to retroactively return to pre-existing environments and convert them over to code.&lt;/p&gt;

&lt;p&gt;Terraform can import pre-existing resources into a state file, which then allows Terraform to manage those resources with a configuration file. However, this process is still in its infancy stage and is actively being improved upon by Hashicorp. As of right now, Terraform cannot automatically generate code based on existing infrastructure. The import process included creating configuration files by hand, then importing the existing resources via the Terraform command line. Another caveat currently is that only a single resource can be imported into a state file at a time. &lt;/p&gt;

&lt;p&gt;In this guide, we walk through the process of importing pre-existing infrastructure into Terraform. First, we deploy some infrastructure with Azure CLI and then import it into a state file to be managed by Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on setting up Azure Cloud Shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, we will be importing some pre-existing infrastructure into Terraform. Before we can walk through the import process, we will need some existing infrastructure in our Azure account. Below is a list of commands to run in Azure CloudShell using Azure CLI in the Bash environment. The Azure CLI commands deploy a resource group, network security group, virtual network, and subnets. You can copy the entire configuration below and paste it directly into Azure CloudShell to deploy everything all at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group create --name rg-terraform --location eastus

az network vnet create \
  --name vnet-terraform-eastus-1 \
  --resource-group rg-terraform \
  --address-prefix 10.0.0.0/16 \
  --subnet-name subnet-terraform-10.0.0.0_24 \
  --subnet-prefix 10.0.0.0/24

az network nsg create -g rg-terraform -n nsg-terraform

az network nsg rule create -g rg-terraform --nsg-name nsg-terraform -n Allow-HTTPS --priority 100 \
    --source-address-prefixes '*' --source-port-ranges 443 \
    --destination-address-prefixes '*' --destination-port-ranges 443 --access Allow \
    --protocol Tcp --description "Allow HTTPS"

az network nsg rule create -g rg-terraform --nsg-name nsg-terraform -n Allow-HTTP --priority 110 \
    --source-address-prefixes '*' --source-port-ranges 80 \
    --destination-address-prefixes '*' --destination-port-ranges 80 --access Allow \
    --protocol Tcp --description "Allow HTTP"

az network vnet subnet create -g rg-terraform --vnet-name vnet-terraform-eastus-1 \
    -n subnet-terraform-10.0.1.0_24 \
    --address-prefixes 10.0.1.0/24  \
    --network-security-group nsg-terraform

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We should now have a resource group with a network security group, virtual network, and two subnets. In the next steps we will walk through how to import this infrastructure into Terraform. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Simple Import &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We will start by importing a resource group into Terraform. Remember, we can only import one resource at a time. To import a resource, we need to have a Terraform configuration file already built for that resource. The configuration file allows us to link the resource identifier used by Terraform to the resource identifier used in Azure. To import our resource group, we will create the following configuration in a &lt;code&gt;main.tf&lt;/code&gt; file within Azure CloudShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
    version="1.38.0"
}

# create resource group
resource "azurerm_resource_group" "rg"{
    name = "rg-terraform"
    location = "eastus"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The syntax to perform an import with Terraform uses the following format for Azure resources using the &lt;code&gt;terraform import&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform import &amp;lt;Terraform Resource Name&amp;gt;.&amp;lt;Resource Label&amp;gt; &amp;lt;Azure Resource ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We already have the resource block name of our resource group, which is &lt;code&gt;azurerm_resource_group&lt;/code&gt;, according to the Azure Terraform provider. We also need to reference the given local name that we are calling our resource group block, which in our example is &lt;code&gt;rg&lt;/code&gt;. Now we need the resource ID of the resource group in Azure to tell Terraform we want to import this item from Azure. To retrieve the resource ID, we can look up the properties of the &lt;code&gt;rg-terraform&lt;/code&gt; resource group in the Azure portal, or we can use the following command in the Azure CloudShell to display the ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group show --name rg-terraform
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output looks like the following, copy the ID of the resource group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": "/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform",
  "location": "eastus",
  "managedBy": null,
  "name": "rg-terraform",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we have all the information we need to import our resource group into a Terraform state file. In the same directory as our &lt;code&gt;main.tf&lt;/code&gt; file,  we need to run &lt;code&gt;terraform init&lt;/code&gt; to download the plugin for the Azure provider before we can perform the import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;terraform init&lt;/code&gt; has completed, we are good to run &lt;code&gt;terraform import&lt;/code&gt; with our Terraform and Azure identifiers. The import command inspects the &lt;code&gt;main.tf&lt;/code&gt; file and the Azure environment to ensure those IDs are relevant. Then imports information about the resource into a state file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform import azurerm_resource_group.rg /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can see the output indicating the import was successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Importing from ID "/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform"...
azurerm_resource_group.rg: Import prepared!
  Prepared azurerm_resource_group for import
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform]

Import successful!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, let's confirm that our resource group is indeed in the state file by running &lt;code&gt;cat terraform.tfstate&lt;/code&gt; to display the contents. We can see that the resource group is in the state file with the resource ID that we specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": 4,
  "terraform_version": "0.12.23",
  "serial": 1,
  "lineage": "1377f481-dc7d-7da9-45aa-a40f007ebb3d",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "rg",
      "provider": "provider.azurerm",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform",
            "location": "eastus",
            "name": "rg-terraform",
            "tags": {}
          },
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ=="
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After using &lt;code&gt;terraform import&lt;/code&gt;, it is a good idea to run &lt;code&gt;terraform plan&lt;/code&gt; to validate that the configuration in the &lt;code&gt;main.tf&lt;/code&gt; file matches the resource that imported. When we run &lt;code&gt;terraform plan&lt;/code&gt; we want to see output indicating that there are no changes in the plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the plan has been successfully validated and reports no changes between our &lt;code&gt;main.tf&lt;/code&gt; and the current state, we can now deem this configuration as good and store it in our source control repo, as it now contains the configuration for live infrastructure. &lt;/p&gt;

&lt;p&gt;Had we configured our &lt;code&gt;main.tf&lt;/code&gt; to specify a resource group in the westus2 location, even though the actual resource is in eastus, we would still be allowed to import the resource, and the state file would contain the correct eastus location of our resource group in Azure. However, if we ran &lt;code&gt;terraform plan&lt;/code&gt;, the plan would indicate that a rebuild of the resource group would need to occur to match the resource configuration in the &lt;code&gt;main.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # azurerm_resource_group.rg must be replaced
-/+ resource "azurerm_resource_group" "rg" {
      ~ id       = "/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform" -&amp;gt; (known after apply)
      ~ location = "eastus" -&amp;gt; "westus2" # forces replacement
        name     = "rg-terraform"
      ~ tags     = {} -&amp;gt; (known after apply)
    }

Plan: 1 to add, 0 to change, 1 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is why it's crucial to run a &lt;code&gt;terraform plan&lt;/code&gt; after the &lt;code&gt;terraform import&lt;/code&gt; to validate that the configuration and infrastructure are up to date. If the &lt;code&gt;main.tf&lt;/code&gt; displays changes when running the &lt;code&gt;terraform plan&lt;/code&gt;, there is a risk with using that configuration file to apply changes in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Complex Imports &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The example of importing a resource group is defined as a &lt;strong&gt;simple import&lt;/strong&gt;. However, resources that contain several resources within them are deemed as &lt;strong&gt;complex imports&lt;/strong&gt;. An example of this would be a virtual network that contains subnets or a network security group that contains security rules. Both of these resources contain multiple child resources. It is important to be aware of child resources when importing these components. We must capture all the child resources for each resource in the &lt;code&gt;main.tf&lt;/code&gt; terraform configuration file, or they will be removed when running &lt;code&gt;terraform apply&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Below is the Terraform configuration for importing our network security group and virtual network. Notice the child resources they both contain. Copy the configuration below and save over the previous &lt;code&gt;main.tf&lt;/code&gt; we used to import the resource group in step 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
    version="1.38.0"
}

# create resource group
resource "azurerm_resource_group" "rg"{
    name = "rg-terraform"
    location = "eastus"
}


resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-terraform"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name


  security_rule {
    name                       = "Allow-HTTPS"
    description                = "Allow HTTPS"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "443"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "Allow-HTTP"
    description                = "Allow HTTP"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "80"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}



resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-terraform-eastus-1"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = ["10.0.0.0/16"]


  subnet {
    name           = "subnet-terraform-10.0.0.0_24"
    address_prefix = "10.0.0.0/24"
  }

  subnet {
    name           = "subnet-terraform-10.0.1.0_24"
    address_prefix = "10.0.1.0/24"
    security_group = azurerm_network_security_group.nsg.id
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We need the resource IDs of our network security group and virtual network. We could retrieve this information from the Azure portal, or we can type in the following two commands to get them from Azure CloudShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az network nsg show -g rg-terraform -n nsg-terraform

az network vnet show -g rg-terraform -n vnet-terraform-eastus-1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we use &lt;code&gt;terraform import&lt;/code&gt; for each resource specifying their Terraform resource block identifier and Azure resource ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform import azurerm_network_security_group.nsg /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/networkSecurityGroups/nsg-terraform

terraform import azurerm_virtual_network.vnet /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/virtualNetworks/vnet-terraform-eastus-1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;terraform import&lt;/code&gt; is successful for our network security group and virtual network, we can run &lt;code&gt;cat terraform.tfstate&lt;/code&gt; to confirm they are now in the state file. The last test is to run &lt;code&gt;terraform plan&lt;/code&gt; to validate that our &lt;code&gt;main.tf&lt;/code&gt; holds the correct configuration settings for our resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform]
azurerm_network_security_group.nsg: Refreshing state... [id=/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/networkSecurityGroups/nsg-terraform]
azurerm_virtual_network.vnet: Refreshing state... [id=/subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/virtualNetworks/vnet-terraform-eastus-1]

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The plan output shows no changes, which means our &lt;code&gt;main.tf&lt;/code&gt; is solid and can now be used to manage this infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Import Modules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we know how to import existing resources into Terraform, how do we go about importing a module? &lt;/p&gt;

&lt;p&gt;Let's set up a module folder to create a module for the configuration we made in step 2 and test importing it into a state file. In the current directory where we performed the tasks in step 2, we will create a subfolder called &lt;strong&gt;module&lt;/strong&gt; using the following directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;testingimportfolder
    └── main.tf
    └── terraform.tfstate
    └── terraform.tfstate.backup
    └───module
          └── main.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The main.tf consists of a resource block for the Azure provider and a module resource block with the source argument pointing to the parent directory. The source argument is telling our module to use the &lt;code&gt;main.tf&lt;/code&gt; in the directory above it. This is not the ideal folder structure for a normal in production module, but for the sake of demonstrating importing a module with very little pre-setup, the module subfolder works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
    version="1.38.0"
}

module "importlab" {
    source = "../"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Importing a module into a state file is similar to importing resources. However, we need to import each resource that the module configures. For our example, since we are just re-using the &lt;code&gt;main.tf&lt;/code&gt; file that we created in step 2, we need to import the same three resources. But, we need to change the resource identifier on the Terraform configuration side to declare that we are using a module to manage these resources. We can do this by appending our module name to the beginning of each resource identifier, which ends up looking like &lt;code&gt;module.importlab.&amp;lt;resource&amp;gt;&lt;/code&gt;. While in the module folder directory, run &lt;code&gt;terraform init&lt;/code&gt; to initialize the directory and pull down the Azure provider. Then run &lt;code&gt;terraform import&lt;/code&gt; with the following syntax to import the three resources managed by the &lt;code&gt;importlab&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform import module.importlab.azurerm_resource_group.rg /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform

terraform import module.importlab.azurerm_network_security_group.nsg /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/networkSecurityGroups/nsg-terraform

terraform import module.importlab.azurerm_virtual_network.vnet /subscriptions/c8f32571-82db-4a89-923b-7e149764bae3/resourceGroups/rg-terraform/providers/Microsoft.Network/virtualNetworks/vnet-terraform-eastus-1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After importing the three module resources, we can run &lt;code&gt;cat terraform.tfstate&lt;/code&gt; to see the contents of the state file. We see our module resource is present along with the resources that it manages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": 4,
  "terraform_version": "0.12.23",
  "serial": 3,
  "lineage": "919aec3c-da13-c83a-a846-c7d1838e13ef",
  "outputs": {},
  "resources": [
    {
      "module": "module.importlab",
      "mode": "managed",
      "type": "azurerm_network_security_group",
      "name": "nsg",
      "provider": "module.importlab.provider.azurerm",
      "instances": [
        {
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can validate our configuration by running &lt;code&gt;terraform plan&lt;/code&gt;. The plan output should state no changes in infrastructure, indicating that we now have our module configuration imported into Terraform state.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Remote State &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We can use &lt;code&gt;terraform import&lt;/code&gt; with either a local or remote state. Initially, we could have configured a remote backend at the beginning of this guide and imported all of our resources into a remote state file. However, some might like to manipulate a state file locally and then copy it up to their remote state location after they have a valid configuration. For this purpose, we will demonstrate migrating our newly imported local state over to an Azure storage account backend. To create an Azure storage account with a storage container, run the following commands in Azure CloudShell:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure to use an externally unique name for the storage account, or Azure will error out when deploying one.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group create --location eastus --name rg-terraformstate

az storage account create --name terrastatestorage2134 --resource-group rg-terraformstate --location eastus --sku Standard_LRS

az storage container create --name terraformstate --account-name terrastatestorage2134

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To copy our state file over to the storage account, we will create an additional file called &lt;code&gt;backend.tf&lt;/code&gt; in the modules folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module
   └── main.tf
   └── backend.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;backend.tf&lt;/code&gt; file contains the following code to direct our Terraform configuration to save its state to our storage container. Copy the code below and save it to &lt;code&gt;backend.tf&lt;/code&gt; inside the module folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraformstate"
    storage_account_name = "terrastatestorage2134"
    container_name       = "terraformstate"
    key                  = "testimport.terraform.tfstate"
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we run &lt;code&gt;terraform init&lt;/code&gt; in the modules folder and select &lt;code&gt;yes&lt;/code&gt; to copy our current state file over to the Azure storage account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing modules...

Initializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "azurerm" backend. No existing state was found in the newly
  configured "azurerm" backend. Do you want to copy this state to the new "azurerm"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes


Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

Terraform has been successfully initialized!

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our state is now safely stored in the Azure storage account, where the state files for our other infrastructure should be (don't use local state in production). If we wanted to double check, we can use the &lt;code&gt;terraform state list&lt;/code&gt; command to display the resources in our remote state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.importlab.azurerm_network_security_group.nsg
module.importlab.azurerm_resource_group.rg
module.importlab.azurerm_virtual_network.vnet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our pre-existing infrastructure has now been imported and saved in our remote state container to be managed by Terraform going forward.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As you can see, importing existing infrastructure into Terraform can be awkward and tedious. There is not a fully ironed out process for it yet. However, converting pre-existing infrastructure over to be managed by Terraform is worth the time. The benefits gained through "everything in code" will most likely outweigh the time spent on importing infrastructure.&lt;/p&gt;

&lt;p&gt;This process can also be used as a learning experience for employees or team members just starting with Terraform. Following documented procedures for onboarding infrastructure into Terraform can get them well acquainted with how Terraform works with the state file and Azure infrastructure.&lt;/p&gt;

&lt;p&gt;In the next article, we will go deep into the weeds of testing and walk through how to get started with testing our Terraform code. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Functions, Expressions, and Loops</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Sun, 16 Aug 2020 19:08:49 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-functions-expressions-and-loops-363g</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-functions-expressions-and-loops-363g</guid>
      <description>&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Functions&lt;/li&gt;
&lt;li&gt;Step 2 — Complex Expressions&lt;/li&gt;
&lt;li&gt;Step 3 —  Loops&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we start building out infrastructure with Terraform, the logic used to build interlinking modules becomes more complicated. This is why it's essential to understand the options and capabilities of HCL (Hashicorp Configuration Language). Not only will this make it easier to create the necessary logic flow in our code, but we can also optimize our configurations. Writing a Terraform configuration in a different way can potentially provide a performance boost in deployment. &lt;/p&gt;

&lt;p&gt;Understanding how to create loops and advanced expressions can give our code a cleaner look and provide our teammates with a more precise understanding of the infrastructure when reading the Terraform configuration. Remember, our Terraform code is not meant to just deploy our infrastructure and get the job done. The goal of HCL is to bridge the gap between human and machine-friendly interfaces by providing code that is easy to read and work well with the command line at the same time. &lt;/p&gt;

&lt;p&gt;In this guide, we are going to create a module and learn how to use functions, expressions, and loops in our Terraform configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you'd like to follow along with the concepts in this guide, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on how to set this up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Functions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;HCL contains built-in functions that can be called within an expression to logically create values for our arguments. With Terraform, we can use an interactive console for testing our Terraform expressions instead of having to run our Terraform configuration each time. Simply type in the following syntax where Terraform is installed. This works in Azure Cloud Shell as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform console
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You will be directed to the Terraform console, which will display a &lt;code&gt;&amp;gt;&lt;/code&gt;. You may test some of these function examples in this guide in your own Terraform console. There are many &lt;a href="https://www.terraform.io/docs/configuration/functions.html"&gt;built-in functions&lt;/a&gt;, but we will go over the most commonly used ones:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower&lt;/strong&gt; is useful for values that need to always be lowercased like storage account names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lower("TFStoragesta")
tfstoragesta

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Replace&lt;/strong&gt; is useful for manipulating naming schemes or number formats of certain values. The format is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;replace(string, substring, replacement)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A simple use case would be replacing a string in a text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; replace("Luke likes CloudSkills", "likes", "loves")
Luke loves CloudSkills
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A more practical use case would be using the &lt;code&gt;replace&lt;/code&gt; function with a regex query to swap out any subscription ID on a resource ID string with a specific subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; replace("/subscriptions/f8c37571-4325-4a97-977b-7a216e64bae3/resourceGroups/rg-remotestatetfc", "/[0-9A-Fa-f]{8}-(?:[0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/", "c1c37531-2355-4a57-947b-4e236e64bae3")
/subscriptions/c1c37531-2355-4a57-947b-4e236e64bae3/resourceGroups/rg-remotestatetfc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Min&lt;/strong&gt; and &lt;strong&gt;max&lt;/strong&gt; are used to find the highest and lowest values in a set of numbers, which can be useful for determining the largest or smallest resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; max(32,64,99)
99
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; min (32,128,1024)
32
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Split&lt;/strong&gt; can be used to get two string values out of a single value. For example, a &lt;code&gt;Standard_LRS&lt;/code&gt; storage type string can be split up for the &lt;code&gt;azurerm_storage_account&lt;/code&gt; resource that takes the following two arguments for &lt;code&gt;account_teir&lt;/code&gt; and &lt;code&gt;account_replication_type&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_storage_account" "example" {
  name                = "storageaccountname"
  resource_group_name = azurerm_resource_group.example.name

  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could take a variable input of &lt;code&gt;Standard_LRS&lt;/code&gt; and use split to get each value reducing the number of variables needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; split("_","Standard_LRS")
[
  "Standard",
  "LRS",
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Element&lt;/strong&gt; can then be used to access each index of the list by wrapping our split function with an element function and specifying the index value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; element(split("_","Standard_LRS"), 0)
Standard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could then write our resource block like the following if we created an input variable for the &lt;code&gt;Standard_LRS&lt;/code&gt; string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_storage_account" "example" {
  name                = "storageaccountname"
  resource_group_name = azurerm_resource_group.example.name

  location                 = azurerm_resource_group.example.location
  account_tier             = element(split("_","var.storage_type"), 0)
  account_replication_type = element(split("_","var.storage_type"), 1)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Coalesce&lt;/strong&gt; will take a series of arguments and return the first one that isn't null or an empty string &lt;code&gt;""&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; coalesce("40", "50", "20")
40

&amp;gt; coalesce( "", "50", "20")
50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This can be useful for creating a set of optional variables and selecting the one that contains a value:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There are several configurations in this article that will be truncated by a &lt;code&gt;...&lt;/code&gt; to reduce the amount of code in this article.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_virtual_machine" "main" {
  name                  = "${var.prefix}-vm"
  location              = azurerm_resource_group.main.location
  resource_group_name   = azurerm_resource_group.main.name
  network_interface_ids = [azurerm_network_interface.main.id]
  vm_size               = coalesce( var.small, var.medium, var.large)
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Length&lt;/strong&gt; returns the numerical length of a string, list, or map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; length(["32GB", "64GB"])
2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is very useful to get the number of an item. For the example above, we can determine that we have two disk sizes, and we will be requiring 2 disks to be created. We could then feed this value into a loop that we will demo later in this guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setproduct&lt;/strong&gt; will display all possible combinations of a given set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; setproduct(["development", "staging", "production"], ["EastUS", "WestUS"])
[
  [
    "development",
    "EastUS",
  ],
  [
    "development",
    "WestUS",
  ],
  [
    "staging",
    "EastUS",
  ],
  [
    "staging",
    "WestUS",
  ],
  [
    "production",
    "EastUS",
  ],
  [
    "production",
    "WestUS",
  ],
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;File&lt;/strong&gt; will read the contents of a file and return it as a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; file("${path.module}/test.ps1")
#This is an empty PS1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is very useful when using the &lt;code&gt;template_file&lt;/code&gt; data source type, which allows you to insert variables into your files and then pass them into another resource for use. Below we have a &lt;code&gt;test.ps1&lt;/code&gt; file that will set the DNS addresses of a server. We have two variables inserted into our PowerShell script represented by &lt;code&gt;${DNS_Server1}&lt;/code&gt; and &lt;code&gt;${DNS_Server2}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# test.ps1
Set-DnsClientServerAddress -InterfaceIndex 12 -ServerAddresses ("${DNS_Server1}","{DNS_Server2}")
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we can use the &lt;code&gt;template_file&lt;/code&gt; resource to specify the &lt;code&gt;test.ps1&lt;/code&gt; script and replace the &lt;code&gt;${DNS_Server1}&lt;/code&gt; and &lt;code&gt;${DNS_Server2}&lt;/code&gt; variables inside the script with their respective values declared in the &lt;code&gt;vars&lt;/code&gt; block. We can then reference the newly transformed &lt;code&gt;test.ps1&lt;/code&gt; file that includes the proper values for the DNS servers by using &lt;code&gt;data.template_file.init.rendered&lt;/code&gt; in the &lt;code&gt;custom_data&lt;/code&gt; attribute of a virtual machine resource block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "template_file" "init" {
  template = "${file("./test.ps1")}"
  vars = {
    DNS_Server1 = "10.0.0.1"
    DNS_Server2 = "10.0.0.2"
  }

resource "azurerm_virtual_machine" "main" {
...
  os_profile {
      computer_name  = "myserver"
      admin_username = "adminuser"
      admin_password = "badpassword123"
      custom_data = data.template_file.init.rendered
    }
...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Functions are a great way to manipulate the data in our configurations, however, sometimes there is a need to include more logic into our code. This is where expressions come into play. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Complex Expressions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Expressions are used to compute the values in our HCL configurations. Complex expressions allow us to provide additional logic to our resources deployed. Below are some examples of popular expressions we can use in our code:&lt;/p&gt;

&lt;h5&gt;
  
  
  Operators
&lt;/h5&gt;

&lt;p&gt;Operators either combine the result of two values and produce a third or take one value and transform it. The typical operators used in configurations are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Equal&lt;/strong&gt; &lt;code&gt;a==b&lt;/code&gt; if &lt;code&gt;a&lt;/code&gt; is equal to &lt;code&gt;b&lt;/code&gt; then the result is true. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Equal&lt;/strong&gt; &lt;code&gt;a != b&lt;/code&gt; if &lt;code&gt;a&lt;/code&gt; does not equal &lt;code&gt;b&lt;/code&gt; then the result is true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Either Or&lt;/strong&gt;  &lt;code&gt;a || b&lt;/code&gt;  if either &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; is true then the result is true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both True&lt;/strong&gt;  &lt;code&gt;a &amp;amp;&amp;amp; b&lt;/code&gt; if both &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are true then the result is true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not True&lt;/strong&gt; &lt;code&gt;!a&lt;/code&gt; if a is false then the result is true.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Conditions
&lt;/h5&gt;

&lt;p&gt;Conditions determine the value based on the result of two bool (true or false) expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;condition ? true : false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is typically used to set up a default value in a Terraform configuration. For example, if we have a virtual machine module and would like to provide the option to deploy a VM to a different location than it's resource group, we could create a variable for &lt;code&gt;vm_location&lt;/code&gt; and set a default for &lt;code&gt;""&lt;/code&gt; like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "vm_location" {
    type = string
    description = "Azure location of terraform server environment"
    default = ""

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In our virtual machine resource block, we could then use a conditional expression for the &lt;code&gt;location&lt;/code&gt; argument. The condition expression provides the logic to use the value for &lt;code&gt;var.vm_location&lt;/code&gt; if it is not an empty string. If &lt;code&gt;var.vm_location&lt;/code&gt; is still an empty string because the user of the module never specified one, then the condition expression provides the logic to default to the location of our resource group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_virtual_machine" "main" {
  name                  = "MyVMName"
  location              = var.vm_location != "" ? var.vm_location : azurerm_resource_group.main.location
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  For Expressions
&lt;/h5&gt;

&lt;p&gt;For expressions allow us to take a value and change it. We can use this to iterate over a list of values and modify them. For example, we can obtain a list of subnets from the &lt;code&gt;azurerm_virtual_network&lt;/code&gt; data source and use a &lt;code&gt;for&lt;/code&gt; expression to iterate over each subnet and change the output formatting it with a lowercase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "azurerm_virtual_network" "example" {
  name                = "LukeLab-NC-Prod-Vnet"
  resource_group_name = "NetworkingTest-RG"
}

output "subnets" {
  value = [for s in data.azurerm_virtual_network.example.subnets : lower(s)]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When running this config the output is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:

subnets = [
  "lukeapp-nc-prod-subnet",
  "lukeapp5-nc-prod-subnet",
  "lukeapp4-nc-prod-subnet",
  "lukeapp3-nc-prod-subnet",
  "lukeapp2-nc-prod-subnet",
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice the for expression is wrapped in &lt;code&gt;[]&lt;/code&gt;. This will give us a tuple output. We can also change the output type to an object by wrapping our for expression in &lt;code&gt;{}&lt;/code&gt; instead. Also we must include two result expressions using the &lt;code&gt;=&amp;gt;&lt;/code&gt; symbol to separate them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "azurerm_virtual_network" "example" {
  name                = "LukeLab-NC-Prod-Vnet"
  resource_group_name = "NetworkingTest-RG"
}

output "subnets" {
  value = {for s in data.azurerm_virtual_network.example.subnets : s =&amp;gt; lower(s)}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output then changes to an object type with out two results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:

subnets = {
  "LukeApp-NC-Prod-Subnet" = "lukeapp-nc-prod-subnet"
  "LukeApp2-NC-Prod-Subnet" = "lukeapp2-nc-prod-subnet"
  "LukeApp3-NC-Prod-Subnet" = "lukeapp3-nc-prod-subnet"
  "LukeApp4-NC-Prod-Subnet" = "lukeapp4-nc-prod-subnet"
  "LukeApp5-NC-Prod-Subnet" = "lukeapp5-nc-prod-subnet"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can also perform more advanced logic with our for expressions. We can filter out our results by including an if statement followed by some logic. In this example, we are only listing subnets that are not an empty string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[for s in data.azurerm_virtual_network.example.subnets : lower(s) if s != ""
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For a more advanced example of this, we can use &lt;code&gt;regexall&lt;/code&gt; to filter our results to only include subnets that meet our regex requirements. In this example, we are selecting only subnets with LukeApp 2-4 in the name. Also, note we are wrapping our &lt;code&gt;regexall&lt;/code&gt; function with a &lt;code&gt;length&lt;/code&gt; function and then checking to ensure that it is greater than 0. This is the recommended way to validate a string that matches the regex requirements. If we were to skip using &lt;code&gt;length&lt;/code&gt; the if expression would error out when the regex query doesn't match the other subnets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "azurerm_virtual_network" "example" {
  name                = "LukeLab-NC-Prod-Vnet"
  resource_group_name = "NetworkingTest-RG"
}

output "subnets" {
  value = [for s in data.azurerm_virtual_network.example.subnets : s if length(regexall("LukeApp[2-4]-NC-Prod-Subnet", s)) &amp;gt; 0]

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run this, we get only the subnets that matched our regex statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:

subnets = [
  "LukeApp4-NC-Prod-Subnet",
  "LukeApp3-NC-Prod-Subnet",
  "LukeApp2-NC-Prod-Subnet",
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Splat Expressions
&lt;/h5&gt;

&lt;p&gt;Splat expressions are a cleaner way of performing some of the same tasks that we can do with a &lt;code&gt;for&lt;/code&gt; expression. In our previous &lt;code&gt;for&lt;/code&gt; expression example, we listed subnets of a virtual network like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[for s in data.azurerm_virtual_network.example.subnets : s]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Instead, we can use a splat expression like so. The [*] symbol directs us to iterate over all the elements in the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data.azurerm_virtual_network.example[*].subnets
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could also reference the index of the attribute to select the first subnet in the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data.azurerm_virtual_network.example[*].subnets[0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Complex expressions are a great way to add logic to our Terraform configurations and allow us to create modules that can be versatile. For example, if we wanted all VMs deployed in the development environment to use slower, less expensive disk types, we could code the logic with a conditional expression. Using a variable &lt;code&gt;var.environment&lt;/code&gt; to state the environment we want to deploy to, we could then reference that variable to conditionally decide to use either the &lt;code&gt;Standard_LRS&lt;/code&gt; or &lt;code&gt;Premium_LRS&lt;/code&gt; disk type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_virtual_machine" "main" {
  ...

  storage_os_disk {
    name              = "myosdisk1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = var.environment == "Development" ? "Standard_LRS" :  "Premium_LRS"
  }
  ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Functions and complex expressions can do a lot of our heavy lifting, however there is one more tool in our tool belt that can help simplify our code. In the next section we will cover loops. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 —  Loops &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Loops in Terraform can help us in many ways. We can quickly iterate through a collection of components with a loop. We can also scale our resources efficiently using loops. Here are some of the ways we can loop through resources in Terraform:&lt;/p&gt;

&lt;h5&gt;
  
  
  Count
&lt;/h5&gt;

&lt;p&gt;Count is a very popular technique in Terraform configurations. It allows us to create multiple instances of a resource block. Almost all resource blocks can use the &lt;code&gt;count&lt;/code&gt; attribute. Below is an example of an &lt;code&gt;azurerm_resource_group&lt;/code&gt; resource block that has the &lt;code&gt;count&lt;/code&gt; attribute set to &lt;code&gt;3&lt;/code&gt;. This tells Terraform to create this resource three times. Also note, we are modifying the name of the resource group each time; otherwise, our code would error out because we can't have resource groups with duplicate names. We are using the &lt;code&gt;count.index&lt;/code&gt; variable, which is accessible only when the &lt;code&gt;count&lt;/code&gt; argument is used. &lt;code&gt;count.index&lt;/code&gt; specifies the index value of our resource, which is the number assigned to that resource's "spot in the list". The index numbering starts at 0. So if we are deploying a resource with a &lt;code&gt;count&lt;/code&gt; of &lt;code&gt;3&lt;/code&gt; we will have three indexes of 0,1, and 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "main" {
  count = 3
  name     = "rg-MyResourceGroup-${count.index}"
  location = "West US 2"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform plan&lt;/code&gt; against this configuration we can see that we will be deploying three resource groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform will perform the following actions:

  # azurerm_resource_group.main[0] will be created
  + resource "azurerm_resource_group" "main" {
      + id       = (known after apply)
      + location = "westus2"
      + name     = "rg-MyResourceGroup-0"
    }

  # azurerm_resource_group.main[1] will be created
  + resource "azurerm_resource_group" "main" {
      + id       = (known after apply)
      + location = "westus2"
      + name     = "rg-MyResourceGroup-1"
    }

  # azurerm_resource_group.main[2] will be created
  + resource "azurerm_resource_group" "main" {
      + id       = (known after apply)
      + location = "westus2"
      + name     = "rg-MyResourceGroup-2"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Count is used for conditional logic as well. If we set the &lt;code&gt;count&lt;/code&gt; value of a resource to &lt;code&gt;0&lt;/code&gt;, Terraform will deploy 0 copies of that resource. This is handy for creating the logic to deploy resources only in certain circumstances. In the example below, we have a bool variable for &lt;code&gt;bootdiag_storage&lt;/code&gt;. We also have a resource block for azure storage to be used for our boot diagnostic storage. Notice the &lt;code&gt;count&lt;/code&gt; argument now has a conditional expression to evaluate if the &lt;code&gt;bootdiag_storage&lt;/code&gt; variable is true or not. If the variable is set to true, set the &lt;code&gt;count&lt;/code&gt; argument to &lt;code&gt;1&lt;/code&gt;, indicating that we will deploy this resource. If the variable is set to false, &lt;code&gt;count&lt;/code&gt; is then set to &lt;code&gt;0&lt;/code&gt; indicating that the &lt;code&gt;azure_storage_account&lt;/code&gt; resource will not be deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  features {}
}

variable "bootdiag_storage" {
  type = bool
  description = "Enter the name of the boot diagnostic storage account if one is desired"
  default = false
}

resource "azurerm_resource_group" "rg" {

  name     = "rg-MyResourceGroup"
  location = "West US 2"
}

resource "azurerm_storage_account" "bootdiag" {
  count = var.bootdiag_storage ? 1 : 0

  name                     = "myterraformvmsadiag"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "GRS"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  For_each
&lt;/h5&gt;

&lt;p&gt;For_each loops allow us to iterate through a list or map. This allows us to have cleaner code. For example, instead of creating an &lt;code&gt;azurerm_network_security_rule&lt;/code&gt; resource for each rule we want to create for our NSG, we can simply create a map variable called &lt;code&gt;inbound_rules&lt;/code&gt;. We can then use &lt;code&gt;for_each&lt;/code&gt; to loop through each item in our map and create a rule for each one. Also, note we are using &lt;code&gt;each.key&lt;/code&gt; and &lt;code&gt;each.value&lt;/code&gt; variables in our configuration to specify the key and value pairs in our map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  features {}
}

variable "inbound_rules" {
  type = map
  description = "A map of allowed inbound ports and their priority value"
  default = {
    101 = 3389
    102 = 22
    103 = 443

  }
}

resource "azurerm_resource_group" "rg" {

  name     = "rg-mytesourcegroup"
  location = "West US 2"
}

resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-mysecuritygroup"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_network_security_rule" "nsg_rule" {
  for_each = var.inbound_rules
  name                        = "port_${each.value}"
  priority                    = each.key
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = each.value
  source_address_prefix       = "*"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.rg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform plan&lt;/code&gt; we can see all our NSG rule resource blocks will be created with the values from our map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform will perform the following actions:

  # azurerm_network_security_group.nsg will be created
  + resource "azurerm_network_security_group" "nsg" {
      + id                  = (known after apply)
      + location            = "westus2"
      + name                = "nsg-mysecuritygroup"
      + resource_group_name = "rg-mytesourcegroup"
      + security_rule       = (known after apply)
    }

  # azurerm_network_security_rule.nsg_rule["101"] will be created
  + resource "azurerm_network_security_rule" "nsg_rule" {
      + access                      = "Allow"
      + destination_address_prefix  = "*"
      + destination_port_range      = "3389"
      + direction                   = "Inbound"
      + id                          = (known after apply)
      + name                        = "port_3389"
      + network_security_group_name = "nsg-mysecuritygroup"
      + priority                    = 101
      + protocol                    = "Tcp"
      + resource_group_name         = "rg-mytesourcegroup"
      + source_address_prefix       = "*"
      + source_port_range           = "*"
    }

  # azurerm_network_security_rule.nsg_rule["102"] will be created
  + resource "azurerm_network_security_rule" "nsg_rule" {
      + access                      = "Allow"
      + destination_address_prefix  = "*"
      + destination_port_range      = "22"
      + direction                   = "Inbound"
      + id                          = (known after apply)
      + name                        = "port_22"
      + network_security_group_name = "nsg-mysecuritygroup"
      + priority                    = 102
      + protocol                    = "Tcp"
      + resource_group_name         = "rg-mytesourcegroup"
      + source_address_prefix       = "*"
      + source_port_range           = "*"
    }

  # azurerm_network_security_rule.nsg_rule["103"] will be created
  + resource "azurerm_network_security_rule" "nsg_rule" {
      + access                      = "Allow"
      + destination_address_prefix  = "*"
      + destination_port_range      = "443"
      + direction                   = "Inbound"
      + id                          = (known after apply)
      + name                        = "port_443"
      + network_security_group_name = "nsg-mysecuritygroup"
      + priority                    = 103
      + protocol                    = "Tcp"
      + resource_group_name         = "rg-mytesourcegroup"
      + source_address_prefix       = "*"
      + source_port_range           = "*"
    }

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "westus2"
      + name     = "rg-mytesourcegroup"
    }

Plan: 5 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h5&gt;
  
  
  Dynamic Blocks
&lt;/h5&gt;

&lt;p&gt;Resources that contain repeatable configuration blocks can make use of dynamic blocks. For example, the &lt;code&gt;azurerm_virtual_machine&lt;/code&gt; resource includes a &lt;code&gt;storage_data_disk&lt;/code&gt; block for defining the configuration of our VM data disks. This block can be used multiple times within &lt;code&gt;azurerm_virtual_machine&lt;/code&gt; to specify multiple data disks. Instead of repeating the same block structure over and over again, we can use dynamic blocks to simplify our code. This is done by prefixing the block with &lt;code&gt;dynamic&lt;/code&gt; and using &lt;code&gt;for_each&lt;/code&gt; to iterate through our &lt;code&gt;storage_data_disk&lt;/code&gt; block. &lt;/p&gt;

&lt;p&gt;In this example, we have a variable &lt;code&gt;disk_size_gb&lt;/code&gt; that can accept a string of comma-separated numbers to indicate the size of the disks to add. So if we wanted three disks that are sized as 32GB, 64GB, and 128GB we would set this variable to "32,64,128" in our &lt;code&gt;terraform.tfvars&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;for_each&lt;/code&gt; argument underneath our &lt;code&gt;dynamic storage_data_disk&lt;/code&gt; block, we are using the &lt;code&gt;split&lt;/code&gt; function to separate our disk size values and iterate through each one. We are using &lt;code&gt;storage_data_disk.value&lt;/code&gt; to reference the value of each number in &lt;code&gt;disk_size_gb&lt;/code&gt; fo each disk size. Also, we are using &lt;code&gt;storage_data_disk.key&lt;/code&gt; to reference the index number of our &lt;code&gt;storage_data_disk&lt;/code&gt; block and using that value in the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;lun&lt;/code&gt; arguments to provide unique values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
variable "disk_size_gb" {
  description = "Size of disks in GBs. Choose multiple disks by comma separating each size"
  type        = string
  default = "64"
}

# Create virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = var.servername
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_D2s_v3"
...
  dynamic storage_data_disk {
    for_each = split(",", var.disk_size_gb)
      content {
        name              = "${var.servername}_datadisk_${storage_data_disk.key}"
        create_option     = "Empty"
        lun               = storage_data_disk.key
        disk_size_gb      = storage_data_disk.value
        managed_disk_type = "StandardSSD_LRS"
      }
  }
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform plan&lt;/code&gt; against this configuration we get the following output specifying the &lt;code&gt;storage_data_disk&lt;/code&gt; blocks for our three disks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; # azurerm_virtual_machine.vm will be created
  + resource "azurerm_virtual_machine" "vm" {
      + availability_set_id              = (known after apply)
      + delete_data_disks_on_termination = false
      + delete_os_disk_on_termination    = false
      + id                               = (known after apply)
      + license_type                     = (known after apply)
      + location                         = "westus2"
      + name                             = "vmterraform"
      + network_interface_ids            = (known after apply)
      + resource_group_name              = "rg-terraexample"
      + tags                             = (known after apply)
      + vm_size                          = "Standard_D2s_v3"

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + type         = (known after apply)
        }

      + os_profile {
          + admin_password = (sensitive value)
          + admin_username = "joeblow"
          + computer_name  = "vmterraform"
          + custom_data    = "c991e3a089d3ad26ec85af93f698c375db0933f2"
        }

      + os_profile_windows_config {
          + enable_automatic_upgrades = false
          + provision_vm_agent        = true

          + additional_unattend_config {
              + component    = "Microsoft-Windows-Shell-Setup"
              + content      = (sensitive value)
              + pass         = "oobeSystem"
              + setting_name = "FirstLogonCommands"
            }
          + additional_unattend_config {
              + component    = "Microsoft-Windows-Shell-Setup"
              + content      = (sensitive value)
              + pass         = "oobeSystem"
              + setting_name = "AutoLogon"
            }
        }

      + storage_data_disk {
          + caching                   = (known after apply)
          + create_option             = "empty"
          + disk_size_gb              = 32
          + lun                       = 0
          + managed_disk_id           = (known after apply)
          + managed_disk_type         = "StandardSSD_LRS"
          + name                      = "vmterraform_datadisk_0"
          + write_accelerator_enabled = false
        }
      + storage_data_disk {
          + caching                   = (known after apply)
          + create_option             = "empty"
          + disk_size_gb              = 64
          + lun                       = 1
          + managed_disk_id           = (known after apply)
          + managed_disk_type         = "StandardSSD_LRS"
          + name                      = "vmterraform_datadisk_1"
          + write_accelerator_enabled = false
        }
      + storage_data_disk {
          + caching                   = (known after apply)
          + create_option             = "empty"
          + disk_size_gb              = 128
          + lun                       = 2
          + managed_disk_id           = (known after apply)
          + managed_disk_type         = "StandardSSD_LRS"
          + name                      = "vmterraform_datadisk_2"
          + write_accelerator_enabled = false
        }

      + storage_image_reference {
          + offer     = "WindowsServer"
          + publisher = "MicrosoftWindowsServer"
          + sku       = "2019-Datacenter"
          + version   = "latest"
        }

      + storage_os_disk {
          + caching                   = "ReadWrite"
          + create_option             = "FromImage"
          + disk_size_gb              = (known after apply)
          + managed_disk_id           = (known after apply)
          + managed_disk_type         = "Premium_LRS"
          + name                      = "stvmvmterraformos"
          + os_type                   = (known after apply)
          + write_accelerator_enabled = false
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Loops are great for reducing repetitive code and creating scalable resources. However, they can also add additional complexity for others reading the Terraform configuration, so choose wisely when using loops within Terraform code. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this article, we learned about functions, complex expressions, and loops. We reviewed examples and discussed their benefits and use-cases within our Terraform code. The HCL language is both human-readable and machine-friendly enough to perform complex logic described in this guide. However, there is a balance to writing infrastructure code that will not only get the job done but also provide documentation to another team member. Use them where it makes sense but not to the point where code is hard to understand. Remember, simplicity is a crucial component of creating long-lasting automated solutions. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>
Getting Started with Terraform on Azure: Modules</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Sun, 16 Aug 2020 19:01:58 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-modules-1haj</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-modules-1haj</guid>
      <description>&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Module Architecture&lt;/li&gt;
&lt;li&gt;Step 2 — Creating Modules&lt;/li&gt;
&lt;li&gt;Step 3 —  Module Outputs&lt;/li&gt;
&lt;li&gt;Step 4 — Git Modules&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When creating production-grade Terraform configurations, modules are an absolute must. One of the more apparent benefits of using them is that they allow our code to be DRY. DRY is a software development term that stands for Don't Repeat Yourself. The idea is to reduce the amount of repetition in our code. In Terraform, we can create modules to build re-usable components of our infrastructure. For example, we can have a module for SQL servers and a separate one for Virtual Machines. We can then re-use each module to deploy services and build out the infrastructure for various environments.&lt;/p&gt;

&lt;p&gt;Modules should also be used as a way to split up a large environment into smaller components. We don't want to have a single main.tf file with over 1000 lines of code. Splitting up our code into smaller modules allows us to make changes to our environment safely without affecting large chunks of code. Also, by splitting our environment up into modules, we now have pieces of our infrastructure separated into a testable module. The ability to use software development testing practices to test our Terraform code is an enormous benefit of having infrastructure defined in code in the first place. &lt;/p&gt;

&lt;p&gt;Lastly, modules also provide a way for Terraform users to share their configurations either privately or within the Terraform community. In 2019 HCL was the &lt;a href="https://octoverse.github.com/"&gt;3rd fastest-growing programming language on GitHub&lt;/a&gt;, which validates the accelerated adoption of the HashiCorp product stack. &lt;/p&gt;

&lt;p&gt;In this guide, we are going to create a module and learn how to integrate it into our Terraform configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on how to set this up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Module Architecture &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In a real-world Terraform environment, we wouldn't want to re-create the same code over and over again for deploying infrastructure. This would create a large amount of redundancy in our Terraform code. Instead, we would want to break up our Terraform configurations into modules; typically, the best practice is a module for each component. For example, we could create a module for SQL databases that contain all of our configurations for deploying SQL with our needs. We could then re-use that module whenever a SQL database is needed and call it within our Terraform configurations. &lt;/p&gt;

&lt;p&gt;The diagram below demonstrates the strategy of splitting up the various Azure services by component modules. By creating four modules for each service in this environment, we can also re-use the same code in both Dev, QA, and Prod. This practice ensures accurate infrastructure comparisons between each environment throughout each stage of development. We are no longer copying and pasting our code from dev to QA to Prod. Instead, we parameterize our modules to allow us to customize slightly for each environment, such as resource names and networking subnets:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U-IRqOHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbjlyogrt5jffodbllk7.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U-IRqOHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbjlyogrt5jffodbllk7.PNG" alt="UsingModules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a module for each cloud service also allows us to re-use modules in other projects as well. Our Terraform modules turn into building blocks that can be used over and over again to create infrastructure on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Creating Modules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Creating modules in Terraform is very easy; all we need are input variables and a standard configuration of resources. In the example, we are going to create our first module for a storage account. We will start by creating a module folder and then reference that module in another Terraform configuration. We will begin with a folder hierarchy like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraformdemo
    └──modules 
            └──storage-account
                    └── main.tf
                    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Copy the code for the &lt;code&gt;main.tf&lt;/code&gt; and &lt;code&gt;variables.tf&lt;/code&gt; configurations and create each file. We'll place each file according to the directory structure above.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;variables.tf&lt;/code&gt; file contains our input variables. The input variables are the parameters that our module accepts to customize its deployment. For our storage account module, we are keeping it as simple as possible for the example by receiving inputs for the storage account name, location, and resource group:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;variables.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "saname" {
    type = string
    description = "Name of storage account"
}
variable "rgname" {
    type = string
    description = "Name of resource group"
}
variable "location" {
    type = string
    description = "Azure location of storage account environment"
    default = "westus2"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;main.tf&lt;/code&gt; file contains the code for creating a storage account. In the example we only have a small set of arguments for our storage account to keep things simple. However, in a real production environment, we would possibly want to implement network policies as well as logging options. We could then use our module to define the 'standards' for how we want all our storage accounts to be configured:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_storage_account" "sa" {
  name                     = var.saname
  resource_group_name      = var.rgname
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "GRS"

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we will create another &lt;code&gt;main.tf&lt;/code&gt; file at the root of our &lt;code&gt;terraformdemo&lt;/code&gt; folder which will reference our newly created storage account module directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraformdemo
    └── main.tf
    └──modules 
            └──storage-account
                    └── main.tf
                    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the root &lt;code&gt;main.tf&lt;/code&gt;, we call our module using a &lt;code&gt;module&lt;/code&gt; block followed by a string parameter. Inside the block, we need to reference the module that we are using by declaring a &lt;code&gt;source&lt;/code&gt; argument. In this example, we are merely referencing the module in our &lt;code&gt;modules&lt;/code&gt; subfolder, so the path is &lt;code&gt;./modules/storage-account&lt;/code&gt;. We also need to include any required variable inputs for our storage account module. These are the same variables that we created in the &lt;code&gt;variables.tf&lt;/code&gt; file in our storage account modules directory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The storage account name must be unique and no more than 24 characters long or you may run into failures during deployment. &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus"
}

#Create Storage Account
module "storage_account" {
  source    = "./modules/storage-account"

  saname    = "statfdemosa234"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;main.tf&lt;/code&gt; file, we also include the &lt;code&gt;azurerm&lt;/code&gt; provider block. It is best practice to specify the provider at the root module file; that way, all modules that are called will then inherit this provider. When creating modules, try not to include a provider inside the module itself as much as possible. This can cause further complexity and make modules brittle. When we run our &lt;code&gt;terraform init&lt;/code&gt; in the &lt;code&gt;terraformdemo&lt;/code&gt; directory we can see that the module is initialized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing modules...
- storage_account in modules/storage-account

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "azurerm" (hashicorp/azurerm) 1.38.0...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform apply&lt;/code&gt;, it will reference the &lt;code&gt;storage-account&lt;/code&gt; module to create our storage account with the settings we declared in the module input. Also, we can use the same module multiple times in a configuration with a different parameter string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus"
}

#Create Storage Account
module "storage_account" {
  source    = "./modules/storage-account"

  saname    = "statfdemosa234"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}

#Create Storage Account
module "storage_account2" {
  source    = "./modules/storage-account"

  saname    = "statfdemosa241"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We just created our first module. This module is straightforward, however, for more complex scenarios like deploying a Virtual Machine with encrypted disks, a module can be perfect for abstracting all the complexity away with just a few inputs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 —  Module Outputs &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Variable inputs are not the only important part of a module. Outputs are just as important as well. They allow us to transfer information to and from modules so that they can build off of each other. Creating an output for a module is the same process as with a regular Terraform configuration. Create an &lt;code&gt;output.tf&lt;/code&gt; file and use an &lt;code&gt;output&lt;/code&gt; block to declare our output values. Below we are creating an output block for our storage account primary access key so we can store it in an Azure Key Vault after it is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "primary_key" {
    description = "The primary access key for the storage account"
    value = azurerm_storage_account.sa.primary_access_key
    sensitive   = true
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Also note, we are using the &lt;code&gt;sensitive&lt;/code&gt; argument to specify that the &lt;code&gt;primary_access_key&lt;/code&gt; output for our storage account contains sensitive data. Terraform will treat this information as confidential and hide it from the console display when running &lt;code&gt;terraform apply&lt;/code&gt;. This does not protect the value from within a Terraform's state file; it will still be in cleartext, which is why in a real-world production scenario, we would want to use remote state. &lt;/p&gt;

&lt;p&gt;To use a module's output values in another resource, specify the values by referencing it in the &lt;code&gt;module.&amp;lt;modulename&amp;gt;.&amp;lt;outputname&amp;gt;&lt;/code&gt; format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_key_vault_secret" "stasecret" {
  name         = "statfdemosa234-secret"
  value        = module.storage_account.primary_key
  key_vault_id = azurerm_key_vault.kv.id

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 — Git Modules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If we plan to share this module throughout multiple environments, its best practice to put the module in a source control repository, we then get all the benefits of source control for our module like change tracking. Additionally, we also get version tagging. Tagging modules is a best practice because it allows us to "pin" a stable working version of our module to a Terraform configuration. This prevents any breaking changes from affecting configurations that are already in production. &lt;/p&gt;

&lt;p&gt;To use a Terraform module from a git repository, change the &lt;code&gt;source&lt;/code&gt; argument to the git URL. In our example, I have uploaded our storage account module to an Azure DevOps Repo. This is a public git repo and will not require any authentication configuration. We can use the https URL and prefix it with &lt;code&gt;git::&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Storage Account
module "storage_account" {
  source    = "git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1"

  saname    = "tfdemosa23432"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we run a &lt;code&gt;terraform init&lt;/code&gt; we can see in the console output that the module is downloaded from the git repo and saved to the &lt;code&gt;.terraform/modules&lt;/code&gt; local directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
Please install a compatible extension version or remove it.
Initializing modules...
Downloading git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1 for storage_account...
- storage_account in .terraform/modules/storage_account
Downloading git::https://allanore@dev.azure.com/allanore/TerraformModulesExample/_git/TerraformModulesExample?ref=v0.1 for storage_account2...
- storage_account2 in .terraform/modules/storage_account2

Initializing the backend...
...

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Also, if we wanted to use a private Azure Repo with SSH, we could reference our module in the &lt;code&gt;source&lt;/code&gt; argument via an SSH URL like below. We would also need to &lt;a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops&amp;amp;tabs=current-page"&gt;generate&lt;/a&gt; and install the SSH certificate for authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git::git@ssh.dev.azure.com:v3/allanore/TerraformModulesExample/TerraformModulesExample?ref=v0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For using a Terraform module source from a GitHub repo, use the URL to the GitHub project. In the example below, I uploaded our module over to a Github repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Storage Account
module "storage_account" {
  source    = "github.com/allanore/TerraformModulesExample"

  saname    = "tfdemosa23432"
  rgname    = azurerm_resource_group.rg.name
  location  = azurerm_resource_group.rg.location
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The recommended folder structure for a Terraform module repo looks like the following. We have our root module configuration files at the root of our repository directory, which in this example, is &lt;code&gt;storage-account&lt;/code&gt;. We also have a README.md at the root folder. This is a markdown file that contains the information about our module. It's recommended to have README.md files for every Terraform configuration to describe what it is and how it is used. Next, we have our &lt;code&gt;modules&lt;/code&gt; folder, which contains any sub-modules that would be needed to perform additional tasks, for example, configuring Private Link or setting up a Static Website. We also have our &lt;code&gt;examples&lt;/code&gt; directory, which should contain examples of every possible scenario of our module. Lastly, we have our &lt;code&gt;test&lt;/code&gt; folder, which includes test files written in Golang to test our module using the examples from the example folder; we will go more into testing modules in a later article in this series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storage-account
    └── README.md
    └── main.tf
    └── variables.tf
    └── outputs.tf
    ├───modules 
    │     ├──private-link
    │     │       └── README.md
    │     │       └── main.tf
    │     │       └── outputs.tf
    │     │       └── variables.tf
    │     └──static-website
    │             └── README.md
    │             └── main.tf
    │             └── outputs.tf
    │             └── variables.tf 
    ├───examples
    │     ├───public
    │     │       └── README.md
    │     │       └── main.tf
    │     │       └── outputs.tf
    │     │       └── variables.tf
    │     ├──private-link
    │     │       └── README.md
    │     │       └── main.tf
    │     │       └── outputs.tf
    │     │       └── variables.tf 
    │     └──static-website
    │             └── README.md
    │             └── main.tf
    │             └── outputs.tf
    │             └── variables.tf
    └───test
         └── sa_public_test.go
         └── sa_private_test.go
         └── sa_static_website_test.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This module structure is how we can create production-grade Terraform modules that can be used for every project. It seems like a lot of work for creating a module and can be overwhelming; however, start small and slowly build out your module. A complex module can take an experienced developer several months to build. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - Terraform Registry
&lt;/h2&gt;

&lt;p&gt;Building a module can take a long time; however, there are thousands of modules shared by the community that you can take advantage of by using them as a base or just using them on their own. The &lt;a href="https://registry.terraform.io/"&gt;Terraform Registry&lt;/a&gt; is a centralized place for community-made Terraform modules. It is a good idea to check the Terraform Registry before building your own module to save time. This is also a great learning tool since you can also view the project on GitHub and see how the module is done and the logic used behind it. &lt;/p&gt;

&lt;p&gt;A Terraform Registry can also be private and used via Terraform Cloud. The modules that are on the public Terraform Registry can be used by referencing them in the &lt;code&gt;&amp;lt;namespace&amp;gt;/&amp;lt;name&amp;gt;/&amp;lt;provider&amp;gt;&lt;/code&gt; format. In the example below, we are using a &lt;a href="https://registry.terraform.io/modules/InnovationNorway/function-app/azurerm/0.1.2?tab=resources"&gt;module to deploy Azure Functions&lt;/a&gt; from the Terraform Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
  name     = "rg-MyFirstTerraform"
  location = "westus"
}

module "function-app" {
  source  = "InnovationNorway/function-app/azurerm"
  version = "0.1.2"

  function_app_name = "func-terrademo"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
}


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;Terraform init&lt;/code&gt;, the module is automatically downloaded and used during the &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this article, we learned about modules and how we can use them to abstract our Terraform configurations. We went over how to create a module and how to reference the output from a module. We also looked at how to store our modules in a git repository like GitHub and Azure Repos. Lastly, we learned about the Terraform Registry and the community-made modules stored there. &lt;/p&gt;

&lt;p&gt;In the next article, we will learn about more advanced HCL concepts like for loops, operators, and functions, which will allow us to perform more advanced infrastructure deployments. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Remote State</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Wed, 18 Mar 2020 01:21:13 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-remote-state-2d5c</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-remote-state-2d5c</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Remote State with Storage Account&lt;/li&gt;
&lt;li&gt;Step 2 — Remote State with Terraform Cloud&lt;/li&gt;
&lt;li&gt;Step 3 — Accessing Outputs from Remote State&lt;/li&gt;
&lt;li&gt;Step 4 — Partial State Configuration&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Terraform, the state file keeps track of the current state of the infrastructure that is being deployed and managed. All infrastructure details are recorded, which means sensitive information like passwords and secrets may also be stored in the state file. Because of this, it is vital to store the state file in a secure place, not locally on a laptop. This is where &lt;em&gt;remote state&lt;/em&gt; comes into play. Remote state allows Terraform to store the state file in a remote location like an AWS S3 bucket or Azure Storage Account. HashiCorp also offers their own free remote state storage solution in Terraform Cloud. Most of these remote state locations provide some sort of authentication method and encryption at rest, which is much more secure than storing the state file locally on a laptop.&lt;/p&gt;

&lt;p&gt;Not only does storing state remotely provide the benefit of security, but it also provides &lt;em&gt;state locking&lt;/em&gt;. When teams using Terraform start to grow, it's common for multiple users to be applying Terraform code against the same state. This can turn into a conflict and cause the state to become corrupt. State locking is a feature that locks the state file when someone is applying changes so that multiple users can't modify the state at the same time. &lt;/p&gt;

&lt;p&gt;In this guide, we will go over how to set up remote state with an Azure Storage Account and Terraform Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on setting up Azure Cloud Shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Remote State with Storage Account &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When you store the Terraform state file in an Azure Storage Account, you get the benefits of RBAC (role-based access control) and data encryption. To set up the resource group for the Azure Storage Account, open up an &lt;a href="http://shell.azure.com"&gt;Azure Cloud Shell&lt;/a&gt; session and type in the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az group create --location westus2 --name rg-terraformstate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we create our Storage Account using &lt;code&gt;az storage account create&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure to use an externally unique name for the storage account, or Azure will error out when deploying one.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az storage account create --name terrastatestorage2134 --resource-group rg-terraformstate --location westus2 --sku Standard_LRS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have the Storage Account created, we can create a blob storage container to store the state file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az storage container create --name terraformdemo --account-name terrastatestorage2134
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that our Azure Storage Account is set up, we will need to create a &lt;em&gt;backend&lt;/em&gt; block in our Terraform configuration file. The backend tells Terraform where to store the state, either locally or in a remote location. If a backend is not declared, the state is stored locally by default. &lt;/p&gt;

&lt;p&gt;We start with a &lt;code&gt;terraform&lt;/code&gt; block, which is used for providing instructions to the Terraform tool. In our case, we are telling Terraform to store it's state file remotely. Next is the &lt;code&gt;backend&lt;/code&gt; block with the &lt;code&gt;azurerm&lt;/code&gt; &lt;a href="https://www.terraform.io/docs/backends/types/index.html"&gt;backend type&lt;/a&gt;. This backend type requires the resource group for the storage account, the storage account name, the blob container name, and the name of the state file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraformstate"
    storage_account_name = "terrastatestorage2134"
    container_name       = "terraformdemo"
    key                  = "dev.terraform.tfstate"
  }
}


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since we are using Azure Cloud Shell and are automatically authenticated to Azure CLI, there is no need to configure any sort of additional steps for accessing the Azure Storage Account. We could also authenticate to the storage account with an MSI, Storage Key, or SAS token. Check out the &lt;a href="https://www.terraform.io/docs/backends/types/azurerm.html"&gt;azurerm backend documentation&lt;/a&gt; for the configuration arguments for authenticating with these methods. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will save the configuration below to a &lt;code&gt;main.tf&lt;/code&gt; file in Azure Cloud Shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Set up remote state
terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraformstate"
    storage_account_name = "terrastatestorage2134"
    container_name       = "terraformdemo"
    key                  = "dev.terraform.tfstate"
  }
}

#configure azurerm provider
provider "azurerm" {
  version = "1.38"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-remotestatedemo"
    location = "westus2"
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now when we run a &lt;code&gt;terraform init&lt;/code&gt; and then &lt;code&gt;terraform apply&lt;/code&gt; we can see our resource group is created and the state file is saved in the Azure Storage Account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g7U7EDAh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ljyplwd42slto4co8r3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g7U7EDAh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ljyplwd42slto4co8r3h.png" alt="storage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we've set up remote state with an Azure Storage account let's take a look at setting up a remote state in Terraform Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Remote State with Terraform Cloud &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Terraform Cloud is a hosted service that allows for Terraform users to store their state files remotely as well as collaborate on their Terraform code in a team setting. It is also free for small teams. If you don't have a Terraform Cloud account, go ahead and &lt;a href="http://app.terraform.io/"&gt;set one up&lt;/a&gt;. Once you sign up and verify your account, you will be prompted to create an organization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OC2vQ2kH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ymgoiy4hg5vgur12f751.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OC2vQ2kH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ymgoiy4hg5vgur12f751.png" alt="organization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, select the user profile in the upper right corner and choose &lt;strong&gt;User Settings&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hvgYcFKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/meo9m799zloo8wgx0ikz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hvgYcFKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/meo9m799zloo8wgx0ikz.png" alt="usersettings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Tokens&lt;/strong&gt; on the left hand side to create a user token. Click the &lt;strong&gt;Create an API token&lt;/strong&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sOBR4P1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qcq8s8fxndrzcddwu4oj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sOBR4P1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qcq8s8fxndrzcddwu4oj.png" alt="token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we will need to label our API token. In this example, since we are using the token to authenticate the backend to Terraform Cloud, we will name this API token "Terraform Backend". Select &lt;strong&gt;Create API token&lt;/strong&gt; to obtain the key:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EwPaBCIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nfx83mzsyrxli3iuohdy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EwPaBCIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nfx83mzsyrxli3iuohdy.png" alt="tokensecret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the key. Best practices would be to store this key in an Azure Key Vault or a secret management system. We will not be able to look up the value of this token, HashiCorp does not save this part of the key in their system. Select &lt;strong&gt;Done&lt;/strong&gt; once you have copied the key to use for later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1EPDnuUf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7n6pqpd7xw322sbq5h3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1EPDnuUf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7n6pqpd7xw322sbq5h3r.png" alt="tokensecret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we set up our Terraform Cloud environment and created an API token for authenticating the backend, we should have everything we need to store our state in Terraform Cloud. Let's take a look at what the backend configuration looks like in our Terraform configuration.&lt;/p&gt;

&lt;p&gt;The backend configuration looks similar to when we configured Terraform to use an Azure Storage Account for remote state. However, this time the backend type will be set to &lt;code&gt;remote&lt;/code&gt;. We will also need an &lt;code&gt;organization&lt;/code&gt; argument to specify that we want to use the Terraform Cloud organization that we just created as the remote state location. Lastly, we will need to use a &lt;code&gt;workspace&lt;/code&gt; block to specify the name of the workspace that we want to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "remote" {
    organization = "lukelabdemo"

    workspaces {
      name = "terraformdemo"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Workspaces in Terraform Cloud are the way that the system organizes infrastructure. When running Terraform locally, we split up environments by folders, and Terraform executes against the files in that folder. There is not an elegant way to do this with a hosted solution like Terraform Cloud, which is why workspaces are needed. Use workspaces to separate an environment just like you would use directories to split up a Terraform environment locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lukeapp-dev
    └── main.tf
    └── variables.tf
    └── terraform.tfvars
lukeapp-prod
    └── main.tf
    └── variables.tf
    └── terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have our backend configuration defined, how do we use the API token to authenticate? Previously in Terraform, there used to be an environment variable called &lt;code&gt;TFE_TOKEN&lt;/code&gt; that we could just set the token key as and Terraform would automatically use it to authenticate to Terraform Cloud. However, this solution is no longer recommended because there is also a Terraform Enterprise on-premise solution. There is not a built-in way to distinguish between the two, and the token could potentially be sent to Terraform Cloud when it was meant for the on-premise Terraform Enterprise server. Aso, interpolations are not allowed in backend configurations. So using a variable for the token in the backend config and referencing the variable in the token argument would not be an option in this case.&lt;/p&gt;

&lt;p&gt;HashiCorp recommends using the Terraform CLI configuration file to store the token. The CLI configuration file is a &lt;code&gt;.terraformrc&lt;/code&gt; file or &lt;code&gt;terraform.rc&lt;/code&gt; file in Windows. It contains per-user setting configurations for the Terraform CLI. In Windows, this file is located in the &lt;code&gt;%APPDATA%&lt;/code&gt; location of the user. In all other platforms, the file is in the &lt;code&gt;$HOME&lt;/code&gt; directory of the user. In many cases, this file will need to be manually created since it doesn't usually already exist. &lt;/p&gt;

&lt;p&gt;In Azure Cloud Shell we can configure this file through the bash command line. This syntax is also handy for automating the configuration of the Terraform CLI in a CI/CD pipeline. We are using &lt;code&gt;tee&lt;/code&gt; to create a &lt;code&gt;./terraformrc&lt;/code&gt; file in the &lt;code&gt;$HOME&lt;/code&gt; directory. The &lt;code&gt;./terraformrc&lt;/code&gt; file is configured with the token code set as an argument in a credentials block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tee -a $HOME/.terraformrc &amp;lt;&amp;lt; END
credentials "app.terraform.io" {
  token = "TR2LrFWwfzzqfg.atlasv1.zH5UbacqWMKL1GzzPM6DFwK4qUy3HpZypyiBXJczi8Qy4El1m0N1xUVG3sduEX3TgUo"
}
END
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;cat&lt;/code&gt; against the file, we can see that our token is now in the Terraform CLI file for authenticating to &lt;code&gt;app.terraform.io&lt;/code&gt;. This tells Terraform to use the API token for that hostname:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F4DK01Q_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ncbzzz8rwx8pqeedfrhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F4DK01Q_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ncbzzz8rwx8pqeedfrhj.png" alt="cat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since the token is stored in plain text, it is best practice to secure the endpoint that is using the token. In a CI/CD pipeline, we would want to create a task to remove the .terraformrc file as a cleanup task. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below is the complete Terraform configuration for using Terraform Cloud as remote state. Save the code below to a &lt;code&gt;main.tf&lt;/code&gt; file in its own directory in Azure Cloud Shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Set up remote state
terraform {
  backend "remote" {
    organization = "lukelabdemo"

    workspaces {
      name = "terraformdemo"
    }
  }
}

#configure azurerm provider
provider "azurerm" {
  version = "1.38"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-remotestatetfc"
    location = "westus2"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;terraform init&lt;/code&gt; we can see that the workspace is automatically created in Terraform Cloud by selecting the &lt;strong&gt;Workspaces&lt;/strong&gt; menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PDdOj4Np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/olprnuxggwy5nzpgmpqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PDdOj4Np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/olprnuxggwy5nzpgmpqi.png" alt="workspaces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this moment, if we do a &lt;code&gt;terraform apply&lt;/code&gt; on our current configuration. We would see the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Error building AzureRM Client: Azure CLI Authorization Profile was not found. Please ensure the Azure CLI is installed and then log-in with `az login`.

  on main.tf line 13, in provider "azurerm":
  13: provider "azurerm" {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is because Terraform Cloud doesn't just store remote state; it will also remotely execute the Terraform code on its own disposable VM environment, which is called a &lt;em&gt;run&lt;/em&gt;. We have two options for executing our Terraform configuration. We can remotely execute our Terraform code in Terraform Cloud; or we can choose to disable this feature and execute our code in Azure Cloud Shell, which will only use the remote state feature of Terraform Cloud. Below are the steps on how to do either a local execution or a Terraform Cloud execution:&lt;/p&gt;

&lt;h4&gt;
  
  
  Local Execution
&lt;/h4&gt;

&lt;p&gt;To execute our Terraform code in the Azure Cloud Shell session, we need to change the setting on our newly created Terraform Cloud workspace to &lt;em&gt;local execution&lt;/em&gt;. By default, all Terraform Cloud workspaces default to remote execution. To change this setting, navigate to the &lt;strong&gt;Workspaces&lt;/strong&gt; menu and select the workspace to modify. In our case, its &lt;strong&gt;terraformdemo&lt;/strong&gt;. On the right-hand side select &lt;strong&gt;Settings&lt;/strong&gt; and choose &lt;strong&gt;General&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a4M80NiY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vemwie58tteoeleddo8z.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a4M80NiY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vemwie58tteoeleddo8z.PNG" alt="generalsettings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;General Settings&lt;/strong&gt; under &lt;strong&gt;Execution Mode&lt;/strong&gt; select &lt;strong&gt;Local&lt;/strong&gt; and click the &lt;strong&gt;Save settings&lt;/strong&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dVI2oMPa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jtkexe5w29qzp5ah45j0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dVI2oMPa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jtkexe5w29qzp5ah45j0.png" alt="localexecution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we return to Azure CloudShell and run our &lt;code&gt;terraform apply&lt;/code&gt; on our configuration, it will execute in the local shell like normal. Now we can apply Terraform code in this workspace locally unless we change it back to remote. The change we just made to the Terraform Cloud workspace must be made for every new workspace created if the intent is just to use Terraform Cloud to store remote state. There is currently no way to configure this through the Terraform command line yet. This is a pain for automating as it requires manual intervention to change the execution mode settings each time. A trick that can be used to work around this awkward stage with Terraform Cloud is to create another directory called &lt;code&gt;init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;init
    └── main.tf
lukeapp-prod
    └── main.tf
    └── variables.tf
    └── terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;init&lt;/code&gt; folder is a &lt;code&gt;main.tf&lt;/code&gt; file that includes the following configuration, which will create the Terraform Cloud workspace if it doesn't exist and change it to local execution mode upon creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "workspace"{}

terraform {
  backend "local" {
  }
}

#Terraform Cloud provider
provider "tfe" {
  hostname = "app.terraform.io"
}

#Collect all Terraform Cloud workspaces
data "tfe_workspace_ids" "all" {
  names        = ["*"]
  organization = "lukelabdemo"
}

#Check if Workspace still exists
locals{
  check =  lookup(data.tfe_workspace_ids.all.ids, var.workspace, "None")
}

#Create Workspace if it does not exist
resource "tfe_workspace" "test" {
  count = local.check == "None" ? "1" : "0"
  name         = var.workspace
  organization = "lukelabdemo"
  operations = false
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using this workaround in a CI/CD pipeline, we could add a step for setting up the workspace separately and run &lt;code&gt;terraform apply&lt;/code&gt; first using &lt;code&gt;-var&lt;/code&gt; to specify the workspace name to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply -var='workspace=lukeapp-prod'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could then run the standard configuration of our code using the newly created workspace as the remote state, and the workspace will already be configured with local execution mode.&lt;/p&gt;

&lt;h4&gt;
  
  
  Terraform Cloud Execution
&lt;/h4&gt;

&lt;p&gt;To remotely execute our Terraform code through Terraform Cloud's environment, we need to configure Azure Authentication in Terraform Cloud. This can be done through environment variables. We will also need to set up a service principal account to authenticate as. To set up a service principal account open up an Azure Cloud Shell session and type in the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az ad sp create-for-rbac -n "TerraformSP" --role contributor \
  --scopes /subscriptions/f7c32571-81bd-4e97-977b-sf2s8s4bae3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this example, we are configuring the service principal with contributor rights at the root of the subscription. In a real-world production environment, it's best practice to use the least privileges and smallest scope where possible.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We should get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "appId": "ee38c4bf-1c1a-41df-82c8-5bdb1e7be8be",
  "displayName": "TerraformSP",
  "name": "http://TerraformSP",
  "password": "98fc8ef0-6451-40ad-b0f7-89de76911f0a",
  "tenant": "24e2975c-af72-454e-8dc0-234F3S342"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output corresponds to the following environment variables that are required for the azurerm Terraform provider to authenticate with Azure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARM_CLIENT_ID="ee38c4bf-1c1a-41df-82c8-5bdb1e7be8be"
ARM_CLIENT_SECRET="98fc8ef0-6451-40ad-b0f7-89de76911f0a"
ARM_SUBSCRIPTION_ID="f7c32571-81bd-4e97-977b-sf2s8s4bae3"
ARM_TENANT_ID="24e2975c-af72-454e-8dc0-234F3S342"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To set up these environment variables in Terraform Cloud, select the &lt;strong&gt;terraformdemo&lt;/strong&gt; workspace and then select the &lt;strong&gt;Variables&lt;/strong&gt; tab. Under &lt;strong&gt;Environment Variables&lt;/strong&gt; choose &lt;strong&gt;Add Variable&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yu1votj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cua7cad1i5a6277omh5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yu1votj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cua7cad1i5a6277omh5u.png" alt="envariables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will need to add each variable, its best practice to mark these as sensitive with the checkbox. Click &lt;strong&gt;Save variable&lt;/strong&gt; to save it. Then add the rest of the required environment variables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w7CHPM_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/orv4vdugv8sqv17fssv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w7CHPM_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/orv4vdugv8sqv17fssv4.png" alt="setvariables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should have four environment variables saved when complete:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R6gTnx5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dkosb0jzmgpkl5sojwv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R6gTnx5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dkosb0jzmgpkl5sojwv0.png" alt="completevariables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are ready to run our configuration in Terraform Cloud. In Azure Cloud Shell run &lt;code&gt;terraform apply&lt;/code&gt; on the configuration that is using the Terraform Cloud remote state. We will see the following output with a link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Preparing the remote apply...

To view this run in a browser, visit:
https://app.terraform.io/app/lukelabdemo/terraformdemo/runs/run-tDkgKaJcyskSd2QQ

Waiting for the plan to start...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we navigate to the provided link, we will be able to see the current &lt;em&gt;run&lt;/em&gt; of our configuration in Terraform Cloud. The run will display the plan output of our configuration. We can also either &lt;strong&gt;Confirm &amp;amp; Apply&lt;/strong&gt; from within Terraform Cloud, or confirm from the Azure Cloud Shell session. The output in the Azure Cloud Session is streamed back to the local console, so we could either manage the &lt;code&gt;terraform apply&lt;/code&gt; through either Terraform Cloud or Azure Cloud Shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2_6n1JjB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lxsxidlynm13nhvh9ow7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2_6n1JjB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lxsxidlynm13nhvh9ow7.png" alt="runs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform Cloud shell has additional features like providing costs of resources during &lt;code&gt;terraform plan&lt;/code&gt;. There is also a configuration governance feature called Sentinel, which is very powerful. However, a significant confining element of using the Terraform Cloud for runs is that there are networking constraints. If we were using Terraform provisioners that require access to some network components like SSH into a VM, it would not be possible to use Terraform runs since the Terraform code is executed in HashiCorp's own VM environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Accessing Outputs from Remote State &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Not only can remote state be used for storing information. But it can also be used for retrieving it as well. Terraform can reference the output information from other states. &lt;/p&gt;

&lt;p&gt;We can use a &lt;em&gt;data source&lt;/em&gt; block to allow for information to be collected and used within the Terraform configuration. Each provider typically offers its own set of data sources. To reference other state files from Terraform Cloud, we would need to modify our current configuration to include an output. In this example, we created an output block containing the resource group that is created in the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Set up remote state
terraform {
  backend "remote" {
    organization = "lukelabdemo"

    workspaces {
      name = "terraformdemo"
    }
  }
}

#configure azurerm provider
provider "azurerm" {
  version = "1.38"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-remotestatetfc"
    location = "westus2"
}

#output
output "rg" {
  value = azurerm_resource_group.rg
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To reference that output from another Terraform configuration. We will use a data source block for the &lt;code&gt;terraform_remote_state&lt;/code&gt; type and name it &lt;code&gt;terraformdemo&lt;/code&gt;. We also need to include the backend type that we are sourcing from along with the required arguments for the specific backend type. In this case, we need the organization and workspace. In our example, we are creating a virtual network and referencing the resource group information from the Terraform Cloud remote state in the previous configuration. We are referencing the output in our expression using &lt;code&gt;data.terraform_remote_state.terraformdemo.outputs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "terraform_remote_state" "terraformdemo" {
  backend = "remote"
  config = {
    organization = "lukelabdemo"
    workspaces = {
      name = "terraformdemo"
    }
  }
}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-terraformtfctest"
    address_space       = ["10.0.0.0/22"]
    location            = data.terraform_remote_state.terraformdemo.outputs.rg.location
    resource_group_name = data.terraform_remote_state.terraformdemo.outputs.rg.name
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The same concepts apply for an Azure Storage Account backend type. To access the backend of our Azure Storage Account, we follow the same format but reference the &lt;code&gt;azurerm&lt;/code&gt; backend type with it's required arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "terraform_remote_state" "terraformdemo" {
  backend = "azurerm"
  config = {
    resource_group_name  = "rg-terraformstate"
    storage_account_name = "terrastatestorage2134"
    container_name       = "terraformdemo"
    key                  = "dev.terraform.tfstate"
  }

}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-terraformsatest"
    address_space       = ["10.1.0.0/22"]
    location            = data.terraform_remote_state.terraformdemo.outputs.rg.location
    resource_group_name = data.terraform_remote_state.terraformdemo.outputs.rg.name
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Referencing remote state outputs can make it easier to combine resources from separate environments or projects. However, this causes a tight coupling between the state files and can cause issues with the maintenance of our configurations. This forces us to ensure that both projects are compatible with each other and always have the proper outputs. This is not practical or scalable in large environments managed by Terraform. Instead, have the config look up the data source by name or tag or another means. This also helps when building integrations tests for your Terraform configurations. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Partial State Configuration &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When omitting required arguments in a Terraform backend configuration, this puts the state into what is called a &lt;em&gt;partial configuration&lt;/em&gt;. This is extremely useful for keeping sensitive information out of source control. It is also great for configuring a Terraform backend in a CI/CD pipeline. For example, if we were to configure an Azure Storage Account remote state backend, we could simply use the following in our &lt;code&gt;backend&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {}
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could then fill in the necessary arguments through variables in our CI/CD system to populate a &lt;code&gt;backend.hcl&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#backend.hcl
resource_group_name  = "rg-terraformstate"
storage_account_name = "terrastatestorage2134"
container_name       = "terraformdemo"
key                  = "dev.terraform.tfstate"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can then specify that file during the &lt;code&gt;terraform init&lt;/code&gt; using the &lt;code&gt;-backend-config&lt;/code&gt; option to specify the location of our &lt;code&gt;backend.hcl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init -backend-config=backend.hcl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will feed all of the required backend arguments into the empty &lt;code&gt;backend "azurerm"&lt;/code&gt; block without having to state them in the configuration itself. We could also take the command line approach as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init \
    -backend-config="resource_group_name=rg-terraformstate" \
    -backend-config="storage_account_name=terrastatestorage2134" \
    -backend-config="container_name=terraformdemo" \
    -backend-config="key=dev.terraform.tfstate"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Partial backend configurations are meant to separate sensitive authentication information from our Terraform code. Use them wisely!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this article, we learned about Terraform Remote State. It is the backbone of Terraform and requires careful consideration when planning where and how to manage the Terraform state file. We also learned how to use an Azure Storage Account for storing our state files remotely. We also explored using Terraform Cloud for just remote state as well as remotely executing our code in Hashicorp's environment. &lt;/p&gt;

&lt;p&gt;In the next article, we will dig into modules which are an absolute must when developing-production grade Terraform code. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Variables</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Tue, 11 Feb 2020 01:37:38 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-variables-20gb</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-variables-20gb</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Input Variables&lt;/li&gt;
&lt;li&gt;Step 2 — Maps, Lists, and Objects&lt;/li&gt;
&lt;li&gt;Step 3 — Output Values&lt;/li&gt;
&lt;li&gt;Step 4 — Deploying the Complete Configuration&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reusability is one of the major benefits of Infrastructure as Code. When we codify our networking, storage, and compute in a way that allows for us to reuse the same code to deploy our resources, we can move very fast. Requests for infrastructure that normally take a week or two to complete now take a couple hours because we already have a standard template mapped out for that infrastructure. We also gain the ability to allow for teammates of any skill set to deploy complex infrastructure that's already been defined in code. For example, we no longer need a SQL expert to install and configure SQL services each time it is needed. Now a standard build can be defined in code and re-deployed by anyone. &lt;/p&gt;

&lt;p&gt;This is why it is important to keep in mind the reusability of our code when designing Terraform configurations. In Terraform, we can use variables to allow our configurations to become more dynamic. This means we are no longer hard coding every value straight into the configuration. In this guide we will use the different types of &lt;em&gt;input variables&lt;/em&gt; to parameterize our configuration that we created in the &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;first article of this series&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on how to set this up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Input Variables &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Input variables serve the same purpose as a parameter would for a script. They allow us to parameterize the Terraform configuration so that we can input the values that are required upon deployment to customize our build. &lt;/p&gt;

&lt;p&gt;When creating Terraform configurations, it's best practice to separate out parts of our configuration into individual &lt;code&gt;.tf&lt;/code&gt; files. This gives us better organization and readability. Remember, the code we are creating is also a form of living documentation, so make it pretty for the next person that needs to read it. For defining input variables, it's typical to create a separate &lt;code&gt;variables.tf&lt;/code&gt; file and store the variable configurations in there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;development
└── server
    └── main.tf
    └── variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With the folder structure above, if we ran Terraform in the server directory, it would scan through any &lt;code&gt;.tf&lt;/code&gt; files in that directory and see them as one whole configuration. We could put everything all in one &lt;code&gt;main.tf&lt;/code&gt; file but as our configuration gets bigger this would cause the configuration to be harder to understand and debug. &lt;/p&gt;

&lt;p&gt;A variable is defined in Terraform by using a variable block with a label. The label must be a unique name, you cannot have variables with the same name in a configuration. It is also good practice to include a description and type. The variable type specifies the &lt;a href="https://www.terraform.io/docs/configuration/types.html"&gt;type constraint&lt;/a&gt; that the defined variable will accept. This allows us to validate that we aren't using a string value for a variable that needs to be a number or bool type. Below are two variable blocks for &lt;code&gt;system&lt;/code&gt; and &lt;code&gt;location&lt;/code&gt; both of the type string. The &lt;code&gt;location&lt;/code&gt; variable has an additional &lt;code&gt;default&lt;/code&gt; argument with the &lt;code&gt;westus2&lt;/code&gt; expression. This means that if a value for the location variable is not specified when we run our &lt;code&gt;terraform apply&lt;/code&gt;, it will default to &lt;code&gt;westus2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "system" {
    type = string
    description = "Name of the system or environment"
}

variable "location" {
    type = string
    description = "Azure location of terraform server environment"
    default = "westus2"

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we've declared our variables in the &lt;code&gt;variables.tf&lt;/code&gt; file. We can use the variables in our &lt;code&gt;main.tf&lt;/code&gt; file by calling that variable in the &lt;code&gt;var.&amp;lt;name&amp;gt;&lt;/code&gt; format. So the system variable would be represented as &lt;code&gt;var.system&lt;/code&gt; and the value of our system variable that is is assigned during a &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;terraform plan&lt;/code&gt; can then be referenced in our &lt;code&gt;azurerm_resource_group&lt;/code&gt; resource block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-${var.system}"
    location = var.location
    tags      = {
      Environment = var.system
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Also note that we can also include our variable name as part of a string by wrapping it in curly brackets like &lt;code&gt;${var.system}&lt;/code&gt;. In the example above, this would cause our name argument to be the value of &lt;code&gt;var.system&lt;/code&gt; but prefixed with &lt;code&gt;rg-&lt;/code&gt;. This is extremely useful when dynamically naming resources like a storage account or subnet that should have a naming scheme that includes the system name.&lt;/p&gt;

&lt;p&gt;So now we've declared some variables in our &lt;code&gt;variables.tf&lt;/code&gt; file and we are referencing them in the &lt;code&gt;main.tf&lt;/code&gt; file. How do we actually assign values to these variables we've created? In Terraform, there are several methods for assigning variable values and they all have their use case. The most simple way to assign a value to a variable is using the &lt;code&gt;-var&lt;/code&gt; option in the command line when running our &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;terraform plan&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply -var="system=terraformdemo" -var="location=eastus"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we have many variable values to input we can define them in a variable definition file named &lt;code&gt;terraform.tfvars&lt;/code&gt; in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system ="terraformdemo"
location = "eastus"

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Terraform will automatically load the variable values from the variable definition file if it is named &lt;code&gt;terraform.tfvars&lt;/code&gt; or ends in &lt;code&gt;.auto.tvfars&lt;/code&gt; and placed in the same directory as the other configuration files like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;development
└── server
    └── main.tf
    └── variables.tf
    └── terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can also place variable values in environment variables of the OS that is going to be executing the Terraform config. Terraform will scan the system for any variables that start with &lt;code&gt;TF_VAR_&lt;/code&gt; and use those as variable values for Terraform. This is useful if you are going to be running multiple Terraform configurations on a system. If we were to set the value for the &lt;code&gt;system&lt;/code&gt; variable, we would create an environment variable that looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; export TF_VAR_system=terraformdemo

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For our PowerShell friends it would look more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$Env:TF_VAR_system="terraformdemo"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we know how to declare variables in our configurations and assign values, let's take a look at more complex variable types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Maps, Lists, and Objects&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A map is a collection of values, each value is labeled by a string. The values can be various element types like bool, int, string. However, they must all be of the same type. In the example below is a map variable named &lt;code&gt;managed_disk_type&lt;/code&gt;. In the &lt;code&gt;default&lt;/code&gt; block are two string values, &lt;code&gt;"Premium_LRS"&lt;/code&gt; and &lt;code&gt;"Standard_LRS"&lt;/code&gt; with a named label to identify each one &lt;code&gt;westus2&lt;/code&gt; and &lt;code&gt;eastus&lt;/code&gt;. This allows us to choose a value based on the label name. So, for our example, we have a string key value for &lt;code&gt;westus2&lt;/code&gt; and &lt;code&gt;eastus&lt;/code&gt;. We can use this grouping to define the type of storage we want to use based on the region in Azure. The example use case would be if we wanted servers in &lt;code&gt;westus2&lt;/code&gt; to host our production workloads and use Premium_LRS disk for fast performance while servers hosted in &lt;code&gt;eastus&lt;/code&gt; would be used for disaster recovery workloads and use Standard_LRS disk to save on costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "managed_disk_type" { 
    type = map
    description = "Disk type Premium in Primary location Standard in DR location"

    default = {
        westus2 = "Premium_LRS"
        eastus = "Standard_LRS"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have our map variable defined, we still need to create the logic for which disk to use in our &lt;code&gt;main.tf&lt;/code&gt; file. In the &lt;code&gt;storage_os_disk&lt;/code&gt; block of the &lt;code&gt;azurerm_virtual_machine&lt;/code&gt; resource we would use the &lt;code&gt;lookup&lt;/code&gt; function, which follows the &lt;code&gt;lookup(map, key, default)&lt;/code&gt; format. First, we declare the map variable that we want to lookup, which would be &lt;code&gt;var.managed_disk_type&lt;/code&gt; in this example. Next, we will retrieve our map value based on the key we specify which is &lt;code&gt;var.location&lt;/code&gt;. This variable should resolve to &lt;code&gt;westus2&lt;/code&gt; or whatever location value we input for that variable. If it matches with its key value pair in our map it will retrieve that value. So our example should retrieve &lt;code&gt;Premium_LRS&lt;/code&gt; if our &lt;code&gt;var.location&lt;/code&gt; variable is configured with the &lt;code&gt;westus2&lt;/code&gt; value. If the key value pair does not exist because the &lt;code&gt;var.location&lt;/code&gt; variable doesn't contain either &lt;code&gt;westus2&lt;/code&gt; or &lt;code&gt;eastus&lt;/code&gt; it will default to &lt;code&gt;Standard_LRS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; storage_os_disk {
    name              = "stvm${var.servername}os"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = lookup(var.managed_disk_type, var.location, "Standard_LRS")
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we've created a map variable, we'll create another variable type, list variables. List variable types are a sequence of values that can be any element type as long as they are all of the same type. Variable lists are useful for assigning values that require a list type. In the example below we are using a list variable for &lt;code&gt;vnet_address_space&lt;/code&gt; this attribute can take list values, so declaring a list for the variable allows us to specify a list of addresses to configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "vnet_address_space" { 
    type = list
    description = "Address space for Virtual Network"
    default = ["10.0.0.0/16"]
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We specified a default argument with &lt;code&gt;[10.0.0.0/16]&lt;/code&gt; as the default address space. Notice that it is wrapped in square brackets. This represents a value that is a list. To pass this variable on to our &lt;code&gt;main.tf&lt;/code&gt; we would simply use the &lt;code&gt;var.vnet_address_space&lt;/code&gt; expression just like with a regular string variable. &lt;/p&gt;

&lt;p&gt;Another variable type is the object type. Object types are a structural type that allow for each attribute to have it's own unique type, unlike a map or list. In this example we are declaring an OS variable with the object type for the OS image that we want our VM to use. Object type variables are great for transferring a common set of variable values between modules. We could potentially have another module that outputs this required image information and easily pass it into the OS variable reducing the number of variable blocks needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "os" {
    description = "OS image to deploy"
    type = object({
        publisher = string
        offer = string
        sku = string
        version = string
  })
}    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we use the object type variable in our &lt;code&gt;main.tf&lt;/code&gt; configuration. We will need to specify the &lt;code&gt;os&lt;/code&gt; variable and each attribute inside of it in order to reference each value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storage_image_reference {
    publisher = var.os.publisher
    offer     = var.os.offer
    sku       = var.os.sku
    version   = var.os.version
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Lists, maps, and objects are the three most common complex variable types. They are all great for their specific use cases. In the next section, we will look at how to create an output for our configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Output Values &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Outputs allow for us to define values in the configuration that we want to share with other resources or modules. For example, we could pass on the output information for the public IP address of a server to another process like building a firewall rule. Even if we are using a dynamic public IP, after the configuration is successfully deployed, the value of the public IP address is retrieved and defined as an output. An output block is defined with the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "pip" {
    description = "Public IP Address of Virtual Machine"
    value = azurerm_public_ip.publicip.ip_address
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note, the output parameter string must be unique in the configuration. We could then reference this output from the state file and use it in another configuration which we will go into more detail in the next post in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Deploying the Complete Configuration &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we know how variables and outputs works, lets refactor our configuration for deploying a Virtual Machine and convert it into a dynamic configuration with variables. In Azure Cloud Shell, we will create the four files for our variables and outputs. The directory and file structure will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraformdemo
    └── main.tf
    └── variables.tf
    └── terraform.tfvars
    └── output.tf

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Below are the complete configurations for each file including the changes to make our configuration more dynamic. Copy each configuration and paste it into the Azure Cloud Shell editor using the &lt;code&gt;code &amp;lt;filename&amp;gt;&lt;/code&gt; command to create each file:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-${var.system}"
    location = var.location
    tags      = {
      Environment = var.system
    }
}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-dev-${var.location}-001"
    address_space       = var.vnet_address_space
    location            = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "subnet" {
  name                 = "snet-dev-${var.location}-001 "
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = "10.0.0.0/24"
}

# Create public IP
resource "azurerm_public_ip" "publicip" {
  name                = "pip-${var.servername}-dev-${var.location}-001"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}


# Create network security group and rule
resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-sshallow-${var.system}-001 "
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "nic" {
  name                      = "nic-01-${var.servername}-dev-001 "
  location                  = azurerm_resource_group.rg.location
  resource_group_name       = azurerm_resource_group.rg.name
  network_security_group_id = azurerm_network_security_group.nsg.id

  ip_configuration {
    name                          = "niccfg-${var.servername}"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Create virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = var.servername
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_B1s"

  storage_os_disk {
    name              = "stvm${var.servername}os"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = lookup(var.managed_disk_type, var.location, "Standard_LRS")
  }

  storage_image_reference {
    publisher = var.os.publisher
    offer     = var.os.offer
    sku       = var.os.sku
    version   = var.os.version
  }

  os_profile {
    computer_name  = var.servername
    admin_username = var.admin_username
    admin_password = var.admin_password
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;variables.tf&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "system" {
    type = string
    description = "Name of the system or environment"
}

variable "servername" {
    type = string
    description = "Server name of the virtual machine"
}

variable "location" {
    type = string
    description = "Azure location of terraform server environment"
    default = "westus2"

}

variable "admin_username" {
    type = string
    description = "Administrator username for server"
}

variable "admin_password" {
    type = string
    description = "Administrator password for server"
}

variable "vnet_address_space" { 
    type = list
    description = "Address space for Virtual Network"
    default = ["10.0.0.0/16"]
}

variable "managed_disk_type" { 
    type = map
    description = "Disk type Premium in Primary location Standard in DR location"

    default = {
        westus2 = "Premium_LRS"
        eastus = "Standard_LRS"
    }
}

variable "vm_size" {
    type = string
    description = "Size of VM"
    default = "Standard_B1s"
}

variable "os" {
    description = "OS image to deploy"
    type = object({
        publisher = string
        offer = string
        sku = string
        version = string
  })
}      
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;terraform.tfvars&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system = "terraexample"
servername = "vmterraform"
location = "westus2"
admin_username = "terraadmin"
vnet_address_space = ["10.0.0.0/16","10.1.0.0/16"]
os = {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "16.04.0-LTS"
    version   = "latest"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;output.tf&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "pip" {
    description = "Public IP Address of Virtual Machine"
    value = azurerm_public_ip.publicip.ip_address
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have our directory created with the four configuration files, we will run our &lt;code&gt;terraform init&lt;/code&gt; to initialize the directory and download our provider plugins. We will follow it up with a &lt;code&gt;terraform apply&lt;/code&gt;. Notice that we are now prompted for the Administrator password variable. This because we followed best practices and left the local admin password value out of the &lt;code&gt;terraform.tfvars&lt;/code&gt; file and never set a default value in the &lt;code&gt;variables.tf&lt;/code&gt; file. Terraform will automatically prompt for variables that aren't defined and do not have a default value configured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q_sZRY1F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j83f1phs1asp1djz291w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q_sZRY1F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j83f1phs1asp1djz291w.png" alt="adminpassword"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After typing in a password, confirm the apply to start deploying the infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 2s [id=/subscriptions/f7c32571-81bd-4e23-5321-7e216164bae3/resourceGroups/rg-terraexample]
azurerm_network_security_group.nsg: Creating...
azurerm_virtual_network.vnet: Creating...
azurerm_public_ip.publicip: Creating...
azurerm_public_ip.publicip: Creation complete after 3s [id=/subscriptions/f7c32571-81bd-4e23-5321-7e216164bae3/resourceGroups/rg-terraexample/providers/Microsoft.Network/publicIPAddresses/pip-vmterraform-dev-westus2-001]
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When our VM is successfully built, we will see the public IP address assigned to our VM is generated in the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

pip = 52.183.4.46
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We now have a reusable Terraform configuration for deploying virtual machines into Azure! We can now modify the &lt;code&gt;terraform.tfvars&lt;/code&gt; with different values to customize our deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this article we learned about variables and how we can use them to make our Terraform configurations reusable. We also went over complex variable types like lists, maps, and objects that can be used to populate data dynamically into our Terraform configurations. Variables are a core part of Terraform and should be heavily utilized in most Terraform configurations. Hard coding values into Terraform configurations should be discouraged if they are preventing the code from becoming a reusable tool.&lt;/p&gt;

&lt;p&gt;In the next article we will dig into remote state which must be thoroughly understood before using Terraform in production. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Provisioners</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Tue, 11 Feb 2020 01:18:56 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-provisioners-af5</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-provisioners-af5</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;h1&gt;
  
  
  Table Of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step 1 — Resource Provisioners&lt;/li&gt;
&lt;li&gt;Step 2 — Destroy Provisioners&lt;/li&gt;
&lt;li&gt;Step 3 — Null Resource Provisioners&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Provisioners provide the ability to run additional steps or tasks when a resource is created or destroyed. This is not to be confused as a replacement for configuration management. Tools like Chef, Puppet, and Ansbile are much better suited for configuration management and it's best to leave the heavy lifting to those tools. Provisioners are used to fill the gaps in between. &lt;/p&gt;

&lt;p&gt;Before we go into how to use provisioners in Terraform, let's first discuss the differences between configuration management and Terraform. Configuration management is typically used to enforce desired settings on an operating system or cloud resource. The settings are defined in code and that code is re-applied again and again to enforce those settings for the rest of the resource's life. Terraform is more likened to an &lt;em&gt;orchestrator&lt;/em&gt; where it deploys the infrastructure components and then relies on the configuration management to deploy the desired settings onto the operating system. Terraform is also not designed for configurations to repeatedly run every 15 minutes to maintain it's state. This could be done with a scheduled task or script, however it's not necessary with Terraform as any changes made should be done through the code anyways.&lt;/p&gt;

&lt;p&gt;Unlike configuration management tools, Terraform really shines with immutable infrastructure designs. Building infrastructure that is immutable means building infrastructure that is designed to simply be rebuilt instead of reconfigured or updated. This means that a web server will never be patched or changed, instead it is rebuilt with the new patches or changes and is deployed back into production replacing the old server. Building systems this way reduces the requirement for configuration management since changes are made to the image before deployment, not after the fact. Tools like Packer, which allow for images to be created and defined in code, are used to automate the configuration changes to the image which Terraform is then used to deploy. The idea is that with configuration management, you will never be able to configure an OS 100% to enforce every change on that system, it's much more efficient to rebuild an image to desired state and re-deploy the system. &lt;/p&gt;

&lt;p&gt;However, not all environments can fit into this immutable infrastructure design. Sometimes we are already stuck with a solution in place or are working with an application that can't exist with an immutable infrastructure design. For those cases we need to lean on our configuration management tools to ensure that our servers are not drifting away from the desired state. Provisioners can be used in Terraform to assist with bootstrapping or initializing a configuration management tool onto a server. They can also be used to perform additional customization tasks where the Azure provider is missing the capability. &lt;/p&gt;

&lt;p&gt;Note that provisioners should be a last resort as they can make our Terraform configurations brittle. However, there are some cases where it is still necessary to create a provisioner in order to accomplish a task. In this exercise we will take a look at how to use provisioners in Terraform. We will start by using a provisioner to add a virtual machine into Azure Desired State Configuration, or Azure DSC. This is a configuration management technology in Azure that uses PowerShell to enforce OS settings on a server. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need to set up the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Azure Cloud Shell. Be sure to check out the prerequisites on &lt;a href="https://cloudskills.io/blog/terraform-azure-01"&gt;"Getting Started with Terraform on Azure: Deploying Resources"&lt;/a&gt; for a guide on how to set this up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will also need to set up an Azure Automation account and upload a DSC configuration. We can easily do this in a matter of minutes in Azure Cloud Shell. Open up an Azure Cloud Shell session by going to shell.azure.com. In this example I will be using the PowerShell version since all the syntax in this guide is meant for PowerShell. Start by changing the directory to &lt;code&gt;$home&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $home
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we will need to setup a resource group for our Azure Automation account. Run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-AzResourceGroup -name rg-terraformaa -location westus2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then we will create an Azure Automation account in that resource group using the &lt;code&gt;New-AzAutomationAccount&lt;/code&gt; PowerShell cmdlet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-AZAutomationAccount -name aa-terraformdemo -ResourceGroupName rg-terraformaa -location 'westus2'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We are going to be making a DSC configuration for installing the IIS role. Copy the following code below and paste it into the Azure Cloud Shell. This will create a &lt;code&gt;WebserverConfig.ps1&lt;/code&gt; file on our &lt;code&gt;$home&lt;/code&gt; directory which we will import into our Azure Automation account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"
Configuration WebserverConfig {

    # Import the module for dsc
    Import-DscResource -ModuleName PsDesiredStateConfiguration

    Node 'vmterraform' {

        #ensures that the Web-Server (IIS) feature is enabled.
        WindowsFeature WebServer {
            Ensure = 'Present'
            Name   = 'Web-Server'
        }


    }
}
" &amp;gt; WebserverConfig.ps1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we have the DSC configuration file created, we will import it into the Azure Automation account and immediately start a compilation job. This job takes our configuration file and turns it into a MOF file which contains all the configuration information. The compiled configuration can then be assigned to a node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import-AzAutomationDscConfiguration -AutomationAccountName "aa-terraformdemo"-ResourceGroupName "rg-terraformaa" -SourcePath ".\WebserverConfig.ps1" -force -published | Start-AzureRmAutomationDscCompilationJob
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The compilation job will immediately start. It will take a few minutes to complete, you can check the status with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get-AzureRmAutomationDscCompilationJob -AutomationAccountName "aa-terraformdemo" -ResourceGroupName "rg-terraformaa"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The status will change to &lt;code&gt;completed&lt;/code&gt; if the compile job is successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ResourceGroupName      : rg-terraformaa
AutomationAccountName  : aa-terraformdemo
Id                     : 09743f32-7bfb-4e0b-b18b-db2cc5cb7ead
CreationTime           : 1/25/20 11:29:42 PM +00:00
Status                 : Completed
StatusDetails          :
StartTime              : 1/25/20 11:30:24 PM +00:00
EndTime                : 1/25/20 11:30:32 PM +00:00
Exception              :
LastModifiedTime       : 1/25/20 11:30:32 PM +00:00
LastStatusModifiedTime : 1/1/01 12:00:00 AM +00:00
JobParameters          : {}
ConfigurationName      : WebserverConfig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the compile job is complete, we now have our Azure Automation account set up with a DSC configuration for installing IIS. We are ready to deploy a VM and automatically assign the DSC configuration during deployment. If you want to read more on Azure DSC be sure to check out the &lt;a href="https://docs.microsoft.com/en-us/azure/automation/automation-dsc-overview"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Resource Provisioners &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Provisioners can be tied to a resource which causes them to run after that resource has been successfully created. There are several &lt;a href="https://www.terraform.io/docs/provisioners/index.html#"&gt;provisioner types&lt;/a&gt; that allow for various actions to be taken such as copying a file to a resource, remotely executing a script or command, as well a locally executing a command or script on the endpoint that is running the terraform code. There are also provisioner types that are specifically meant for configuration management tools like Chef, Puppet, and Saltstack. Provisioners are created with a provisioner resource block followed by a provisioner type in the first parameter string. Below is an example of a &lt;code&gt;provisioner&lt;/code&gt; block using the &lt;code&gt;file&lt;/code&gt; provisioner type to copy a PowerShell script to the deployed resource. Inside the &lt;code&gt;provisioner&lt;/code&gt; block is a &lt;code&gt;connection&lt;/code&gt; block. This is used to define how to connect to the resource, in the example &lt;code&gt;winrm&lt;/code&gt; is used but we could also do SSH as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provisioner "file" {
  source      = "./setup.ps1"
  destination = "C:/temp/setup.ps1"

  connection {
    type     = "winrm"
    user     = "Administrator"
    password = "P@ssw0rd"
    host     = "webserver1"
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Creating connections inside provisioners creates a limitation in our Terraform code. We then have to think about network connectivity between the device running the Terraform code and the resources we are deploying. Therefore it's best to avoid using connection blocks when possible. In our example we are going to make use of the AZ PowerShell module cmdlets to assign our VM a DSC configuration. Because we are using Azure Cloud Shell, we are automatically authenticated with Azure and there is no need for additional authentication steps in the provisioner block. If we were to run this code elsewhere, we would need to plan for that in our design. &lt;/p&gt;

&lt;p&gt;We will be running a &lt;code&gt;local-exec&lt;/code&gt; provisioner type since we are running our Terraform configuration from Azure Cloud Shell. On the command argument we will be using &lt;code&gt;&amp;lt;&amp;lt;-&lt;/code&gt; which allows for us to create a multi-line string. The text &lt;code&gt;EOT&lt;/code&gt; stands for end of text. This serves as our marker for when the string ends. We could essentially use any text for this, but EOT is the most commonly used and it's best to stick to standards for readability of code. We are using the &lt;code&gt;Register-AzAutomationDSCNode&lt;/code&gt; cmdlet to register our newly deployed VM with Azure DSC and assign the web server configuration. The &lt;code&gt;@params&lt;/code&gt; is a Powershell technique called splatting used for formatting and easy readability of the cmdlet parameters. Lastly, the &lt;code&gt;interpreter&lt;/code&gt; argument defines the executable or application we want to run. This could be any application on the machine that is running the Terraform code. In our case we are running the configuration from Azure Cloud Shell so we will be using Powershell Core. The strings after &lt;code&gt;pwsh&lt;/code&gt; are the arguments used for the application which will be &lt;code&gt;-command&lt;/code&gt; in our example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provisioner "local-exec" {
    command = &amp;lt;&amp;lt;-EOT
        $params = @{
        AutomationAccountName = "aa-terraformdemo"
        AzureVMName = "${azurerm_virtual_machine.vm.name}"
        ResourceGroupName = "rg-terraformaa"
        NodeConfigurationName = "WebserverConfig.localhost"
        azureVMLocation = "${azurerm_resource_group.rg.location}"
        AzureVMResourceGroup = "${azurerm_resource_group.rg.name}"
        }
        Register-AzAutomationDscNode @params 
   EOT
   interpreter = ["pwsh", "-Command"]
  }

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Below is the entire configuration for deploying our VM. Copy the config below and paste it into a &lt;code&gt;main.tf&lt;/code&gt; file in Azure Cloud Shell using the &lt;code&gt;code main.tf&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus2"
    tags      = {
      Environment = "Terraform Demo"
    }
}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-dev-westus2-001"
    address_space       = ["10.0.0.0/16"]
    location            = "westus2"
    resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "subnet" {
  name                 = "snet-dev-westus2-001"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = "10.0.0.0/24"
}

# Create public IP
resource "azurerm_public_ip" "publicip" {
  name                = "pip-vmterraform-dev-westus2-001"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}


# Create network security group and rule
resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-sshallow-001 "
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "HTTP"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "nic" {
  name                      = "nic-01-vmterraform-dev-001 "
  location                  = "westus2"
  resource_group_name       = azurerm_resource_group.rg.name
  network_security_group_id = azurerm_network_security_group.nsg.id

  ip_configuration {
    name                          = "niccfg-vmterraform"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Create virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = "vmterraform"
  location              = "westus2"
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_B2s"

  storage_os_disk {
    name              = "stvmpmvmterraformos"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Premium_LRS"
  }

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }

  os_profile {
    computer_name  = "vmterraform"
    admin_username = "terrauser"
    admin_password = "Password1234!"
  }
  os_profile_windows_config{
    provision_vm_agent = true
  }

  #Add Virtual Machine to Azure DSC after deployment.
  provisioner "local-exec" {
    command = &amp;lt;&amp;lt;EOT
        $params = @{
        AutomationAccountName = "aa-terraformdemo"
        AzureVMName = "${azurerm_virtual_machine.vm.name}"
        ResourceGroupName = "rg-terraformaa"
        NodeConfigurationName = "WebserverConfig.vmterraform"
        azureVMLocation = "${azurerm_resource_group.rg.location}"
        AzureVMResourceGroup = "${azurerm_resource_group.rg.name}"
        }
        Register-AzAutomationDscNode @params 
   EOT
   interpreter = ["pwsh", "-Command"]
  }


}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will run &lt;code&gt;terraform init&lt;/code&gt; and then &lt;code&gt;terraform apply&lt;/code&gt; to deploy our server. The following output will be displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 1s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform]
azurerm_public_ip.publicip: Creating...
azurerm_virtual_network.vnet: Creating...
azurerm_network_security_group.nsg: Creating...
azurerm_network_security_group.nsg: Creation complete after 3s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform/providers/Microsoft.Network/networkSecurityGroups/nsg-sshallow-001]
azurerm_public_ip.publicip: Creation complete after 4s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform/providers/Microsoft.Network/publicIPAddresses/pip-vmterraform-dev-westus2-001]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;During the deployment, after our VM is created, we can see the provisioner block runs and our PowerShell command is executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_virtual_machine.vm: Still creating... [2m0s elapsed]
azurerm_virtual_machine.vm: Still creating... [2m10s elapsed]
azurerm_virtual_machine.vm: Provisioning with 'local-exec'...
azurerm_virtual_machine.vm (local-exec): Executing: ["pwsh" "-Command" "        $params = @{\n        AutomationAccountName = \"aa-terraformdemo\"\n        AzureVMName = \"vmterraform\"\n        ResourceGroupName = \"rg-terraformaa\"\n        NodeConfigurationName = \"WebserverConfig.vmterraform\"\n        azureVMLocation = \"westus2\"\n        AzureVMResourceGroup = \"rg-MyFirstTerraform\"\n        }\n        Register-AzAutomationDscNode @params \n"]

azurerm_virtual_machine.vm (local-exec): MOTD: Save files to $home/clouddrive for persistence across sessions

azurerm_virtual_machine.vm (local-exec): VERBOSE: Authenticating to Azure ...
azurerm_virtual_machine.vm (local-exec): VERBOSE: Building your Azure drive ...
azurerm_virtual_machine.vm: Still creating... [2m20s elapsed]
azurerm_virtual_machine.vm: Still creating... [2m30s elapsed]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the Terraform deployment has finished, we can check in the Azure Portal and search for Automation Accounts. After selecting the Automation Account &lt;strong&gt;aa-terraformdemo&lt;/strong&gt;, we can select &lt;strong&gt;State Configuration (DSC)&lt;/strong&gt; on the left hand side and see our newly provisioned VM with the WebServer configuration assigned:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dVjmfOpB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fiflwd52ckj98w49xk04.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dVjmfOpB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fiflwd52ckj98w49xk04.PNG" alt="azuredsc"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we go to the assigned Public IP Address in a web browser, we can see that the IIS role is already installed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MnpIAHw2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f6y6rhk9qpzjhhxu07qy.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MnpIAHw2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f6y6rhk9qpzjhhxu07qy.PNG" alt="iis"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've successfully created a Terraform configuration that deploys a VM and assigns a DSC configuration. But what happens when we destroy this VM? Provisioner blocks like the one we created are only deployed when the VM or resource is first created. If we changed the size of the VM or another attribute, the Provisioner block would not run again. If we ran a &lt;code&gt;terraform destroy&lt;/code&gt; on this configuration right now, our VM would not be removed from Azure DSC. In the next section we will look at what we can do to fix this with a &lt;em&gt;destroy provisioner&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Destroy Provisioners &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Destroy provisioners are only executed during a &lt;code&gt;terraform destroy&lt;/code&gt;. This can be useful in cases where additional cleanup is needed for a resource such as VM decommission tasks. A destroy provisioner block looks the same as a regular provisioner block except that there is an additional &lt;code&gt;when = destroy&lt;/code&gt; argument. Below is a snippet of the destroy provisioner that we will be adding :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Remove Virtual Machine from Azure DSC when destroying.
  provisioner "local-exec" {
    when = destroy
    command = &amp;lt;&amp;lt;EOT
        Get-AzAutomationDscNode -ResourceGroupName "rg-terraformaa" -AutomationAccountName "aa-terraformdemo" -Name "${self.name}" | Unregister-AzAutomationDscNode -force
    EOT
   interpreter = ["pwsh", "-Command"]
  }

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The provisioner block uses the &lt;code&gt;Get-AzAutomationDscNode&lt;/code&gt; PowerShell cmdlet to find our VM by name and then pipes it to the &lt;code&gt;Unregister-AzAutomationDSCNode&lt;/code&gt; cmdlet to unregister the VM from Azure DSC. Also notice that we are using the &lt;code&gt;${self.name}&lt;/code&gt; expression to reference our VM name instead of &lt;code&gt;${azurerm_virtual_machine.vm.name}&lt;/code&gt; like we did in the first provisioner block. Terraform does not like destroy provisioner blocks to have dependencies on external resources. This allows for us to have a more stable provisioner because it is not relying upon other resources to exist before the destroy provisioner can be run. The local named value &lt;code&gt;self&lt;/code&gt; references attributes from the resource that the provisioner block resides in. We will add the snippet above to our &lt;code&gt;main.tf&lt;/code&gt; Terraform configuration. The complete configuration should look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus2"
    tags      = {
      Environment = "Terraform Demo"
    }
}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-dev-westus2-001"
    address_space       = ["10.0.0.0/16"]
    location            = "westus2"
    resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "subnet" {
  name                 = "snet-dev-westus2-001 "
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = "10.0.0.0/24"
}

# Create public IP
resource "azurerm_public_ip" "publicip" {
  name                = "pip-vmterraform-dev-westus2-001"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}


# Create network security group and rule
resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-sshallow-001 "
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "HTTP"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "nic" {
  name                      = "nic-01-vmterraform-dev-001 "
  location                  = "westus2"
  resource_group_name       = azurerm_resource_group.rg.name
  network_security_group_id = azurerm_network_security_group.nsg.id

  ip_configuration {
    name                          = "niccfg-vmterraform"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Create virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = "vmterraform"
  location              = "westus2"
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_B2s"

  storage_os_disk {
    name              = "stvmpmvmterraformos"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Premium_LRS"
  }

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }

  os_profile {
    computer_name  = "vmterraform"
    admin_username = "terrauser"
    admin_password = "Password1234!"
  }
  os_profile_windows_config{
    provision_vm_agent = true
  }

  #Add Virtual Machine to Azure DSC after deployment.
  provisioner "local-exec" {
    command = &amp;lt;&amp;lt;EOT
        $params = @{
        AutomationAccountName = "aa-terraformdemo"
        AzureVMName = "${azurerm_virtual_machine.vm.name}"
        ResourceGroupName = "rg-terraformaa"
        NodeConfigurationName = "WebserverConfig.localhost"
        azureVMLocation = "${azurerm_resource_group.rg.location}"
        AzureVMResourceGroup = "${azurerm_resource_group.rg.name}"
        }
        Register-AzAutomationDscNode @params 
   EOT
   interpreter = ["pwsh", "-Command"]
  }

  #Remove Virtual Machine from Azure DSC when destroying.
  provisioner "local-exec" {
    when = destroy
    command = &amp;lt;&amp;lt;EOT
        Get-AzAutomationDscNode -ResourceGroupName "rg-terraformaa" -AutomationAccountName "aa-terraformdemo" -Name "${self.name}" | Unregister-AzAutomationDscNode -force
    EOT
   interpreter = ["pwsh", "-Command"]
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let's run &lt;code&gt;Terraform destroy&lt;/code&gt;. We will see that before the VM is destroyed, the destroy provisioner block is executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_virtual_machine.vm: Destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform/providers/Microsoft.Compute/virtualMachines/vmterraform2]
azurerm_virtual_machine.vm: Provisioning with 'local-exec'...
azurerm_virtual_machine.vm (local-exec): Executing: ["pwsh" "-Command" "        Get-AzAutomationDscNode -ResourceGroupName \"rg-terraformaa\" -AutomationAccountName \"aa-terraformdemo\" -Name \"vmterraform\" |Unregister-AzAutomationDscNode -force\n"]

azurerm_virtual_machine.vm (local-exec): MOTD: Connect to a remote Azure VM: Enter-AzVM

azurerm_virtual_machine.vm (local-exec): VERBOSE: Authenticating to Azure ...
azurerm_virtual_machine.vm (local-exec): VERBOSE: Building your Azure drive ...
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 10s elapsed]
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 20s elapsed]
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 30s elapsed]
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 40s elapsed]
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 50s elapsed]
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...t.Compute/virtualMachines/vmterraform, 1m0s elapsed]
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the destroy process is complete, we will see that the VM is removed from Azure DSC. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FiIoE4FV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3jgvi5b7s7egs9zchz2f.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FiIoE4FV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3jgvi5b7s7egs9zchz2f.PNG" alt="Decom"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have a complete VM configuration automated from deployment to decommission. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Null Resource Provisioners &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Not only can we put provisioner blocks inside of resources like we did with our virtual machine resource. But we can also have them run independently of resource blocks. This can be used for scenarios where we need to run a script or process after several resources are created or if we want to design a provisioner that does not depend on a single resource.&lt;/p&gt;

&lt;p&gt;We will deploy two Azure Container Registries in this example, one for Production and one for Development. Azure Container Registry is a service in Azure that is used for housing container images. We will use a provisioner to import the hello world image from Docker Hub, a public container registry, to our Azure Container Registries once they have been deployed.  &lt;/p&gt;

&lt;p&gt;A provisioner block must still technically reside inside a resource so we will create a &lt;em&gt;null_resource&lt;/em&gt; resource block. Null_resource resource blocks are  used as a "do nothing" type of resource that allow us to run provisioner tasks independently of a resource. Below is a snippet of what our &lt;code&gt;null_resource&lt;/code&gt; resource block will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "null_resource" "import_image" {

  provisioner "local-exec" {
    command = &amp;lt;&amp;lt;EOT
       az acr import --name ${azurerm_container_registry.acr-prod.name} --source docker.io/library/hello-world:latest --image hello-world:latest
       az acr import --name ${azurerm_container_registry.acr-dev.name} --source docker.io/library/hello-world:latest --image hello-world:latest
   EOT
   interpreter = ["pwsh", "-Command"]
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The resource block is declared just like any other resource with the resource type and label. Next, we have our standard provisioner block using &lt;code&gt;local-exec&lt;/code&gt; to run our AZ Cli commands locally from our Cloud Shell session since that's where we will be running our Terraform code in this example. The AZ Cli commands are used to import the hello world container image to each Azure Container Registry. &lt;/p&gt;

&lt;p&gt;The complete configuration will look like the following. Copy the configuration and paste it into a new &lt;code&gt;main.tf&lt;/code&gt;. Make sure that you've either removed the terraform contents created from the previous steps or you are using a new directory for this Terraform code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Resource Group
resource "azurerm_resource_group" "rg" {
  name     = "rg-acrdemo"
  location = "West US"
}

#Create Azure Container Registry
resource "azurerm_container_registry" "acr-prod" {
  name                     = "acrprodregistrycloudskillslab001"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  sku                      = "Standard"
  admin_enabled            = false

}

#Create Azure Container Registry
resource "azurerm_container_registry" "acr-dev" {
  name                     = "acrdevregistrycloudskillslab001"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  sku                      = "Standard"
  admin_enabled            = false

}

#Import Container Image to Azure Container Registries
resource "null_resource" "image" {

  provisioner "local-exec" {
    command = &amp;lt;&amp;lt;EOT
       az acr import --name ${azurerm_container_registry.acr-prod.name} --source docker.io/library/hello-world:latest --image hello-world:latest
       az acr import --name ${azurerm_container_registry.acr-dev.name} --source docker.io/library/hello-world:latest --image hello-world:latest
   EOT
   interpreter = ["pwsh", "-Command"]
  }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will run the standard sequence of &lt;code&gt;terraform init&lt;/code&gt; followed by &lt;code&gt;terraform apply&lt;/code&gt; to deploy our resources. Notice that during &lt;code&gt;terraform init&lt;/code&gt; the &lt;code&gt;null&lt;/code&gt; provider is downloaded. This is because we are using the &lt;code&gt;null_resource&lt;/code&gt; resource block which requires the provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.azurerm: version = "~&amp;gt; 1.41"
* provider.null: version = "~&amp;gt; 2.1"

Terraform has been successfully initialized!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After running &lt;code&gt;terraform apply&lt;/code&gt; we can see that our two Azure Container Registries are created, and the hello world image is automatically imported to each of them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jlrxmkkm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gav5k3x3qf4wl9o238b3.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jlrxmkkm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gav5k3x3qf4wl9o238b3.PNG" alt="ACR"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, what happens if we changed the location of these Azure Container Registries? The resources would be re-created, but the provisioner task would not run again because it only runs the first time we deploy our resources. When designing infrastructure with Terraform, we want to make our configurations as stable as possible for any scenario. To improve this configuration, we will use a &lt;code&gt;triggers&lt;/code&gt; argument to declare that we want our provisioner to run again if any of our Azure Container Registries are modified. The code will look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;triggers = {
     prod = azurerm_container_registry.acr-prod.id
     dev = azurerm_container_registry.acr-dev.id
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We will add this to our current configuration and the &lt;code&gt;main.tf&lt;/code&gt; should look like the following. Copy and paste the code below and overwrite the current &lt;code&gt;main.tf&lt;/code&gt; to include the &lt;code&gt;triggers&lt;/code&gt; argument. We are also going to change the location of our resources to &lt;code&gt;West US 2&lt;/code&gt; by modifying the location of the &lt;code&gt;azurerm_resource_group&lt;/code&gt; resource block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create Resource Group
resource "azurerm_resource_group" "rg" {
  name     = "rg-acrdemo"
  location = "West US 2"
}

#Create Azure Container Registry
resource "azurerm_container_registry" "acr-prod" {
  name                     = "acrprodregistrycloudskillslab001"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  sku                      = "Standard"
  admin_enabled            = false


}

#Create Azure Container Registry
resource "azurerm_container_registry" "acr-dev" {
  name                     = "acrdevregistrycloudskillslab001"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  sku                      = "Standard"
  admin_enabled            = false


}

#Import Container Image to Azure Container Registries
resource "null_resource" "image" {

  triggers = {
     prod = azurerm_container_registry.acr-prod.id
     dev = azurerm_container_registry.acr-dev.id
  }  

  provisioner "local-exec" {
    command = &amp;lt;&amp;lt;EOT
       az acr import --name ${azurerm_container_registry.acr-prod.name} --source docker.io/library/hello-world:latest --image hello-world:latest
       az acr import --name ${azurerm_container_registry.acr-dev.name} --source docker.io/library/hello-world:latest --image hello-world:latest
   EOT
   interpreter = ["pwsh", "-Command"]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we will test this out. Run &lt;code&gt;terraform apply&lt;/code&gt; against this new configuration. Our resources will be destroyed and recreated in the West US 2 region. During the deployment, we will now see our provisioner is re-executed again because of our &lt;code&gt;triggers&lt;/code&gt; argument. In the &lt;code&gt;triggers&lt;/code&gt; argument we specified to re-run the provisioner if any modifications are made to our Azure Container Registry resources. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this article we learned about provisioners in Terraform. We reviewed the concepts of immutable infrastructure and configuration management, and successfully deployed a virtual machine using provisioners to automatically configure Azure DSC on the node. We also created a &lt;code&gt;null_resource&lt;/code&gt; block and used it to execute additional tasks when provisioning two  Azure Container Registries. Provisioners are a useful way to provide additional configuration beyond what the provider can perform. However, provisioners should be considered a last resort. As you can see in our examples, they complicate the deployment process and can make our configurations more complex and brittle. The provisioner blocks cannot truly be accounted for when running &lt;code&gt;terraform plan&lt;/code&gt; since the actions we are taking inside the provisioner block can be anything we specify. Provisioners can also implement network dependencies that prevent Terraform code from being deployed from any environment. Best practice is to lean on the capabilities of Terraform providers or configuration management systems before resorting to using provisioners.&lt;/p&gt;

&lt;p&gt;In the next article we will dig into variables which are a core part of creating long lasting reusable and secure Terraform configurations. &lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Getting Started with Terraform on Azure: Deploying Resources</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Mon, 13 Jan 2020 13:40:13 +0000</pubDate>
      <link>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-deploying-resources-4i59</link>
      <guid>https://forem.com/cloudskills/getting-started-with-terraform-on-azure-deploying-resources-4i59</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Terraform is an open source command line tool that allows users to deploy infrastructure to multiple environments and systems including AWS, Azure, GCP, and even on-premise vSphere. It has a platform agnostic approach that doesn't require programming knowledge. This makes it easy enough for most people to become proficient with Terraform in just 3 - 5 days. &lt;/p&gt;

&lt;p&gt;Not only is Terraform easy to learn, but it is also very universal. Terraform uses &lt;em&gt;providers&lt;/em&gt; to interact with the API's of various Cloud providers, PaaS services, SaaS services, and on premise resources. There are currently &lt;a href="https://www.terraform.io/docs/providers/index.html"&gt;121 providers&lt;/a&gt; on Terraform's official website and the number is increasing steadily. One of the key benefits of Terraform is that it allows for teams to have the power to declaratively deploy infrastructure to multiple systems with a single tool.&lt;/p&gt;

&lt;p&gt;Terraform is a great way to start your Infrastructure as Code journey in Azure. It's an agentless tool, meaning agent software is never installed on deployed resources. It is also masterless, so no need to set up a management server, you can get started within just minutes. In this guide we are going use Terraform to deploy resources to Azure using Azure CloudShell. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, you'll need an &lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure subscription&lt;/a&gt;. Create a free account or use an existing sandbox subscription.&lt;/p&gt;

&lt;p&gt;We will be using the Azure Cloud Shell as Terraform already comes preinstalled in this environment and is the fastest way to get started. To launch Azure Cloud Shell, browse to &lt;a href="https://shell.azure.com"&gt;shell.azure.com&lt;/a&gt;. Select either &lt;strong&gt;Bash&lt;/strong&gt; or &lt;strong&gt;PowerShell&lt;/strong&gt;. This will bring up a Linux container environment with a command line interface to either tool you select. In this example we will use &lt;strong&gt;Bash&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y5KfLaet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kw9wjm92ps5wvtvfos6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y5KfLaet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kw9wjm92ps5wvtvfos6z.png" alt="csbash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, choose the desired subscription to store our Cloud Shell resources in and select &lt;strong&gt;Create Storage&lt;/strong&gt;. This will create a storage account in that subscription which will be used to store our user session data for CloudShell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f10V2ItE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vqqbjoe6yrzpxtlwuygd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f10V2ItE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vqqbjoe6yrzpxtlwuygd.png" alt="cssubscription"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait a few minutes for Azure to provision the storage account. Once completed, we will be redirected to our Azure Cloud Shell environment. To verify the subscription our Cloud Shell environment is using, we can type the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az account show
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If your using the incorrect subscription, or would like to deploy infrastructure to another subscription, type in the following syntax and specify the name or ID of the desired subscription to switch to in the Azure Cloud Shell console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az account set -s "my subscription name"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we are ready to start deploying infrastructure using Terraform&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Create a Terraform Config File
&lt;/h2&gt;

&lt;p&gt;Terraform code is written in HCL, also known as Hashicorp Configuration Language, which is designed to be human readable and simple to understand. It's also a &lt;em&gt;declarative language&lt;/em&gt;, meaning that we declare what we want to be built and Terraform figures out how to build it and "makes it so". For example, if we declare in HCL code that we want two virtual machines, Terraform will build them. However, if we change our mind later and decide we only need one, we simply remove the 2nd virtual machine from the code. Terraform will then remove the 2nd virtual machine from the environment because it is no longer in code. This is important to understand when designing your Terraform configurations. &lt;/p&gt;

&lt;p&gt;The Terraform code is stored in &lt;em&gt;configuration files&lt;/em&gt; that are files with a &lt;code&gt;*.tf&lt;/code&gt; extension. These files can be created in any text editor, in this example we will be using the integrated file editor inside of Azure Cloud Shell to create and edit our configuration files.&lt;/p&gt;

&lt;p&gt;To get started lets make a directory in our Azure Cloud Shell called &lt;code&gt;terraform&lt;/code&gt; and then change our directory to that folder:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir terraform &amp;amp;&amp;amp; cd $_&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we will create a &lt;code&gt;main.tf&lt;/code&gt; file to deploy a resource group to our Azure subscription. In Azure Cloud Shell type the following command while in the newly created &lt;code&gt;terraform&lt;/code&gt; directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;code main.tf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This creates a file named &lt;code&gt;main.tf&lt;/code&gt; and opens an integrated file editor that we can use to create our configuration file in Azure Cloud Shell. There are more efficient ways to develop Terraform configurations, like using Visual Studio Code with the Terraform extension. However, for the ease of this guide, we will use the integrated file editor in Azure Cloud Shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8eKxqyBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9x74yu63yej7lj8o2tqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8eKxqyBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9x74yu63yej7lj8o2tqh.png" alt="blankscode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Terraform the syntax is simple. We declare the components we want to create by defining &lt;em&gt;blocks&lt;/em&gt; and &lt;em&gt;arguments&lt;/em&gt; in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;BLOCK TYPE&amp;gt; "&amp;lt;BLOCK LABEL&amp;gt;" "&amp;lt;BLOCK LABEL&amp;gt;" {
  # Block body
  &amp;lt;IDENTIFIER&amp;gt; = &amp;lt;EXPRESSION&amp;gt; # Argument
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first block we are going to create is the provider block. This block specifies the provider we will use. Like I mentioned previously, providers allow for Terraform to interact with the API's of the environment we are deploying to. In this case we are using the Azure provider. Many providers are maintained by the vendors themselves. However, the Azure provider has a small team of developers at Hashicorp that are dedicated to maintaining it. In our Terraform configuration, we need to specify that we want to use the Azure provider to deploy our resources. A basic Azure provider block looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The provider block contains the information on the type of provider we would like to use. It also only requires a single block label. The &lt;code&gt;auzrerm&lt;/code&gt; provider is the name of the provider we will be specifying in the parameter string. In this example &lt;code&gt;version = "1.38.01&lt;/code&gt; is our only argument. We are stating that we only want version 1.38 of the provider. It is recommended to always include the version on your provider blocks as future releases of the provider may break Terraform code.&lt;/p&gt;

&lt;p&gt;Typically the provider is also the way that we authenticate with that system. In our example we are using Azure Cloud Shell and are already authenticated so there is no need to specify any sort of additional arguments for authenticating. However, if we were running Terraform in a different environment like a laptop or CI/CD pipeline, we would need to either use Azure CLI, a Service Principle Account, or Managed Service Identity for authentication. Check out the documentation for more information on ways to &lt;a href="https://www.terraform.io/docs/providers/azurerm/index.html#authenticating-to-azure"&gt;authenticate with the azurerm provider&lt;/a&gt;. Also, we can use multiple providers in a single configuration file. So if we needed to span multiple systems or clouds in one configuration we would start by specifying the provider and authentication arguments for each system.&lt;/p&gt;

&lt;p&gt;Now that we've gone over the provider block, our next block to create is the resource block. Resource blocks are for the infrastructure objects that we want to declare. In our example we want to create a resource group named &lt;strong&gt;rg-MyFirstTerraform&lt;/strong&gt; in the &lt;strong&gt;westus&lt;/strong&gt; region. We are defining this infrastructure in a resource block and the syntax looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The resource block contains two sets of string parameters. The first string parameter represents the &lt;em&gt;resource type&lt;/em&gt;, which in our case is the &lt;code&gt;azurerm_resource_group&lt;/code&gt; type. For a list of available types of resources to create, check out the &lt;a href="https://www.terraform.io/docs/providers/azurerm/"&gt;azurerm provider documentation&lt;/a&gt;. The second string parameter is the &lt;em&gt;resource name&lt;/em&gt;. The resource name is the name that we are labeling that resource within the Terraform configuration and must be unique for each resource type that is created. So we can't have two &lt;code&gt;azurerm_resource_group&lt;/code&gt; resource types with the name &lt;code&gt;rg&lt;/code&gt; in a single configuration. &lt;/p&gt;

&lt;p&gt;Now we have a complete Terraform configuration that enables us to deploy our resource group into Azure. Copy the following configuration file syntax below and paste it into our Azure Cloud Shell editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To save our code to the &lt;code&gt;main.tf&lt;/code&gt; file in the Cloud Shell integrated editor, press the &lt;code&gt;CTRL + S&lt;/code&gt; keys. Next, press &lt;code&gt;CTRL + Q&lt;/code&gt; to close out of the editor. We've now created our first configuration file. In the next section we will deploy it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Init, Apply, Plan, and Destroy
&lt;/h2&gt;

&lt;p&gt;The first step to deploying our configuration is to &lt;em&gt;initialize&lt;/em&gt; our configuration. This process will create a hidden &lt;code&gt;.terraform&lt;/code&gt; folder that contains our plugins. Our providers will automatically be downloaded and as you will see, each provider in Terraform has its own binary file. Run the following command inside the &lt;code&gt;terraform&lt;/code&gt; directory to perform the initialization:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can see that the azurerm provider plugin binary for version 1.38 is downloaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "azurerm" (hashicorp/azurerm) 1.38.0...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since our &lt;code&gt;terraform init&lt;/code&gt; was successful, we will now run our &lt;em&gt;plan&lt;/em&gt;. A plan is a way for terraform to essentially do a "dry run" on the configuration and provide detailed information on what the deployment would look like. This is a similar concept to running a "-whatif" in PowerShell. To run our plan, we will type in the following syntax:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We should now see our plan in the output. Each resource that will be affected is identified and listed in the output. Anything that is a new resource to be created will have a &lt;code&gt;+&lt;/code&gt; next to it. You will see later when we destroy this resource it will have a &lt;code&gt;-&lt;/code&gt; instead. Towards the bottom we can see the number of items added, changed, or destroyed is specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "westus"
      + name     = "rg-MyFirstTerraform"
      + tags     = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, we can deploy our infrastructure by running our &lt;em&gt;apply&lt;/em&gt;. This will display output similar to &lt;code&gt;terraform plan&lt;/code&gt; and ask for confirmation to apply the configuration. To start, run the following syntax in the &lt;code&gt;terraform&lt;/code&gt; directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A plan will be generated the same as when we ran &lt;code&gt;terraform plan&lt;/code&gt;, review the plan and type &lt;code&gt;yes&lt;/code&gt; to continue. The following output will be displayed indicating that our resource group was created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 2s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that our resource group has been deployed, let's take a look at the &lt;em&gt;state file&lt;/em&gt; that was created. The state file is a &lt;code&gt;*.tfstate&lt;/code&gt; file that holds the current "state" of the resource we just created. This is how Terraform can keep track of changes that it has made or needs to make.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dir
main.tf  terraform.tfstate

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we type &lt;code&gt;cat terraform.tfstate&lt;/code&gt; to display its contents we can see the format and contents of our state file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[secondary_label Output]
{
  "version": 4,
  "terraform_version": "0.12.18",
  "serial": 1,
  "lineage": "74f54b5d-2150-0d28-8631-2330f08a1586",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "rg",
      "provider": "provider.azurerm",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform",
            "location": "westus2",
            "name": "rg-MyFirstTerraform",
            "tags": {}
          },
          "private": "bnVsbA=="
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The state file is meant to live for the entire life cycle of the resource and contains data on the infrastructure that has been provisioned. Because of this, it is highly recommended to store the state file in a remote location. This is a process called &lt;em&gt;remote state&lt;/em&gt; which we will cover in a later article.&lt;/p&gt;

&lt;p&gt;Let's try modifying our configuration and see what happens. We will add a tag to our resource group by modifying our &lt;code&gt;main.tf&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus"
    tags      = {
      Environment = "Terraform Demo"
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When we run our &lt;code&gt;terraform plan&lt;/code&gt; we can see the plan output which shows any resources that are changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be updated in-place
  ~ resource "azurerm_resource_group" "rg" {
        id       = "/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform"
        location = "westus"
        name     = "rg-MyFirstTerraform"
      ~ tags     = {
          + "Environment" = "Terraform Demo"
        }
    }

Plan: 0 to add, 1 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;~&lt;/code&gt; indicates a resource that will be &lt;em&gt;updated in place&lt;/em&gt; meaning an argument will be modified without having to remove the resource and rebuild it. Before we apply the change. Let's modify the config file again and change our resource group to exist in the &lt;code&gt;westus2&lt;/code&gt; region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus2"
    tags      = {
      Environment = "Terraform Demo"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now when we run &lt;code&gt;terraform plan&lt;/code&gt; we can see the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # azurerm_resource_group.rg must be replaced
-/+ resource "azurerm_resource_group" "rg" {
      ~ id       = "/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform" -&amp;gt; (known after apply)
      ~ location = "westus" -&amp;gt; "westus2" # forces replacement
        name     = "rg-MyFirstTerraform"
      ~ tags     = {
          + "Environment" = "Terraform Demo"
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can see a  &lt;code&gt;-/+&lt;/code&gt; next to the &lt;code&gt;azurerm_resource_group.rg&lt;/code&gt; resource. This indicates that terraform will destroy this resource and then re-create it. Terraform already knows that we need to delete the resource group and build it in another region, and it will automatically handle the logistics for us. The &lt;code&gt;# forces replacement&lt;/code&gt; note states that the location is the argument that is causing the resource to be completely replaced.&lt;/p&gt;

&lt;p&gt;It is extremely important to understand how to properly read plan files when modifying infrastructure that is already deployed. Make sure you understand the effects of the changes you are making and if any downtime of the resource is required from the change. It is highly recommended to test changes in a development environment first since it's easy to copy the terraform code and deploy in another environment to test.&lt;/p&gt;

&lt;p&gt;We run &lt;code&gt;terraform apply&lt;/code&gt; and see that our resource group is moved to &lt;code&gt;westus2&lt;/code&gt; (logs truncated for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...e31/resourceGroups/rg-MyFirstTerraform, 40s elapsed]
azurerm_resource_group.rg: Destruction complete after 48s
azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 2s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's tear down our newly deployed resource by running a &lt;em&gt;destroy&lt;/em&gt;. This will delete all the items that we deployed using our Terraform configuration. So, in our example the resource group will be destroyed. Note that a destroy is not yet considered an action that should be performed on infrastructure that is in production. When running a destroy, there is no "undo button", the infrastructure is completely removed. We run a destroy by typing in the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform destroy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We get another plan output indicating which resources will be destroyed indicated by a &lt;code&gt;-&lt;/code&gt; next to them. Also we can see the &lt;code&gt;1 to destroy&lt;/code&gt; in the plan section at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be destroyed
  - resource "azurerm_resource_group" "rg" {
      - id       = "/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform" -&amp;gt; null
      - location = "westus2" -&amp;gt; null
      - name     = "rg-MyFirstTerraform" -&amp;gt; null
      - tags     = {} -&amp;gt; null
    }

Plan: 0 to add, 0 to change, 1 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; to confirm the destroy. We should then get the following output displaying our resource group being destroyed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform]
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...e31/resourceGroups/rg-MyFirstTerraform, 10s elapsed]
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...e31/resourceGroups/rg-MyFirstTerraform, 20s elapsed]
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...e31/resourceGroups/rg-MyFirstTerraform, 30s elapsed]
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...e31/resourceGroups/rg-MyFirstTerraform, 40s elapsed]
azurerm_resource_group.rg: Destruction complete after 47s

Destroy complete! Resources: 1 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we have successfully created and destroyed a resource group in Azure using Terraform. Let's move on to deploying something more exciting. In the next step we will deploy a virtual machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Deploying a Virtual Machine
&lt;/h2&gt;

&lt;p&gt;In Azure, a virtual machine has a few resources that are required in order to build one. We will create a resource in Terraform for the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource group&lt;/li&gt;
&lt;li&gt;Virtual network&lt;/li&gt;
&lt;li&gt;Subnet&lt;/li&gt;
&lt;li&gt;Network Interface&lt;/li&gt;
&lt;li&gt;Virtual Machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We already have the code for the resource group from the previous step. Let's add the following code to the &lt;code&gt;main.tf&lt;/code&gt; for our virtual network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-dev-westus2-001"
    address_space       = ["10.0.0.0/16"]
    location            = "westus2"
    resource_group_name = azurerm_resource_group.rg.name
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We are using the &lt;code&gt;azurerm_virtual_network&lt;/code&gt; resource type with the &lt;code&gt;vnet&lt;/code&gt; resource name. Notice the &lt;code&gt;address_space&lt;/code&gt; value is surrounded in bracket squares &lt;code&gt;[]&lt;/code&gt;. This indicates that the value type is a list and can contain multiple comma separated values. For example we could put in more than one address space such as &lt;code&gt;["10.0.0.0/16,10.1.0.0/16"]&lt;/code&gt; however for this example we will just be using one. Lastly, we will point out the &lt;code&gt;azurerm_resource_group.rg.name&lt;/code&gt; expression. It is referencing the value of another resource's attributes. In this case we are referencing our &lt;code&gt;azurerm_resource_group&lt;/code&gt; type with the resource name of &lt;code&gt;rg&lt;/code&gt; and its &lt;code&gt;name&lt;/code&gt; attribute. The value of the &lt;code&gt;name&lt;/code&gt; attribute is &lt;code&gt;rg-MyFirstTerraform&lt;/code&gt; in our example. Referencing the attribute of our resource like this allow us to build upon information within our resources. Because we are referencing the name value of our resource group, Terraform will automatically know to build the resource group first. This is called an &lt;em&gt;implicit dependency&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, we will add the subnet, network security group, public IP, and virtual machine resources to our config. The complete configuration should look like this. Copy the following below and paste it into the Azure Cloud Shell editor to overwrite our &lt;code&gt;main.tf&lt;/code&gt; to include these changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "azurerm" {
  version = "1.38.0"
}

#create resource group
resource "azurerm_resource_group" "rg" {
    name     = "rg-MyFirstTerraform"
    location = "westus2"
    tags      = {
      Environment = "Terraform Demo"
    }
}

#Create virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "vnet-dev-westus2-001"
    address_space       = ["10.0.0.0/16"]
    location            = "westus2"
    resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "subnet" {
  name                 = "snet-dev-westus2-001 "
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = "10.0.0.0/24"
}

# Create public IP
resource "azurerm_public_ip" "publicip" {
  name                = "pip-vmterraform-dev-westus2-001"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}


# Create network security group and rule
resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-sshallow-001 "
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "nic" {
  name                      = "nic-01-vmterraform-dev-001 "
  location                  = "westus2"
  resource_group_name       = azurerm_resource_group.rg.name
  network_security_group_id = azurerm_network_security_group.nsg.id

  ip_configuration {
    name                          = "niccfg-vmterraform"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

# Create virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = "vmterraform"
  location              = "westus2"
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_B1s"

  storage_os_disk {
    name              = "stvmpmvmterraformos"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Premium_LRS"
  }

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "16.04.0-LTS"
    version   = "latest"
  }

  os_profile {
    computer_name  = "vmterraform"
    admin_username = "terrauser"
    admin_password = "Password1234!"
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice we have our password in plain text in the configuration file. This is not best practice and we are only doing it to demonstrate deploying the virtual machine. We will fix that issue in a later article. Now, let's run &lt;code&gt;terraform init&lt;/code&gt; to initialize our directory. Next, we will run our plan, but we will use the &lt;code&gt;-out&lt;/code&gt; parameter to output our plan to a file. Typically, this is the best practice for automating Terraform in a CI/CD pipeline. This allows for additional unit testing steps to be run against the plan file before deployment. It also guarantees our actions in the plan when we run &lt;code&gt;terraform apply&lt;/code&gt; against the plan file. In this example we named the plan file &lt;code&gt;tfvm&lt;/code&gt;, but it can be named anything that makes logical sense for the environment we are deploying. We will run &lt;code&gt;terraform plan&lt;/code&gt; and create our plan file. The syntax is below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -out=tfvm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the plan is successful, the plan file is created. With the command &lt;code&gt;terraform show&lt;/code&gt; we can specify our plan file name and review the plan or even have another team member review it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform show tfvm

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_network_interface.nic will be created
  + resource "azurerm_network_interface" "nic" {
      + applied_dns_servers           = (known after
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We could also take this plan file and use &lt;code&gt;terraform show -json tfvm&lt;/code&gt; to get our plan in a JSON format. This opens a lot of doors for automating the review of our plan. For example, we could then take the JSON file of our plan and use PowerShell's &lt;code&gt;convertfrom-json&lt;/code&gt; cmdlet to convert it into a PowerShell Object and perform customized Pester testing against our plan files. Also note that these plan files are not encrypted and may possibly contain secrets or passwords in them. It's not best practice to store these and they should only temporarily exist. There are plans on the Terraform roadmap to make these plan files more secure in the future.&lt;/p&gt;

&lt;p&gt;Next, we will run our &lt;code&gt;terraform apply&lt;/code&gt; against the actions in the plan file by specifying the file name. Notice that we won't be prompted to confirm that we want to apply the changes when we run apply this way:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform apply tfvm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We should immediately see the infrastructure starting to build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 1s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform]
azurerm_virtual_network.vnet: Creating...
azurerm_public_ip.publicip: Creating...
azurerm_network_security_group.nsg: Creating...
azurerm_public_ip.publicip: Creation complete after 6s [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform/providers/Microsoft.Network/publicIPAddresses/pip-vmterraform-dev-westus2-001]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When our deployment is complete, we will see the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is where you can start to see the power of Terraform. In a few minutes we have deployed a Virtual Machine with a subnet, virtual network, and public IP address. &lt;/p&gt;

&lt;p&gt;Now that our virtual machine is deployed, lets destroy our infrastructure using a destroy. Like I stated before, this typically is only done in dev and sandbox environments. Now let's destroy our infrastructure, but this time we will use a different method. We will create a &lt;em&gt;destroy plan&lt;/em&gt; which will create a plan file, meant for destroying infrastructure, which we will then run &lt;code&gt;terraform apply&lt;/code&gt; against. To do this run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan -destroy -out=tfvmdestroy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After carefully reviewing our destroy, we can apply the destroy plan file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply tfvmdestroy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now our infrastructure will start to tear down displaying snippets of output like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
azurerm_virtual_machine.vm: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...ft.Compute/virtualMachines/vmterraform, 1m30s elapsed]
azurerm_virtual_machine.vm: Destruction complete after 1m31s
azurerm_network_interface.nic: Destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-3c15c2e3be31/resourceGroups/rg-MyFirstTerraform/providers/Microsoft.Network/networkInterfaces/nic-01-vmterraform-dev-001]
azurerm_network_interface.nic: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...kInterfaces/nic-01-vmterraform-dev-001, 10s elapsed]
azurerm_network_interface.nic: Still destroying... [id=/subscriptions/5c80ecff-4dfe-4fa0-abd4-...kInterfaces/nic-01-vmterraform-dev-001, 20s elapsed]
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once all of our infrastructure is completely destroyed, we will see the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apply complete! Resources: 0 added, 0 changed, 7 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you can see how quickly we can build and tear down infrastructure with Terraform in a matter of minutes. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article we learned how to create Terraform configuration files. We reviewed the concepts of the init, plan, apply, and destroy commands that are most commonly used for deploying Terraform configuration files. We also successfully deployed a virtual machine into Azure with all its networking components. Terraform is a very powerful tool and greatly improves consistency and efficiency with managing infrastructure. Now imagine using the same process we used to deploy infrastructure in Azure with another cloud or on-premise VMware environment (using different providers and resource blocks). &lt;/p&gt;

&lt;p&gt;In the next article we will go even further into deploying virtual machines by using provisioners to customize resources after they are deployed. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>How to Use Pipeline Templates to Manage Infrastructure with Terraform on Azure DevOps</title>
      <dc:creator>Luke Orellana</dc:creator>
      <pubDate>Wed, 08 Jan 2020 17:14:02 +0000</pubDate>
      <link>https://forem.com/cloudskills/how-to-use-pipeline-templates-to-manage-infrastructure-with-terraform-on-azure-devops-29p7</link>
      <guid>https://forem.com/cloudskills/how-to-use-pipeline-templates-to-manage-infrastructure-with-terraform-on-azure-devops-29p7</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Infrastructure as Code is considered a best practice when managing infrastructure in the cloud. Tools like Terraform are becoming increasingly popular due to their ease of use and multi cloud nature. However, adopting the Infrastructure as Code model can become hectic and unmanageable if not organized strategically. With designing any automated process, simplicity is key to creating long lasting solutions. In Azure DevOps, one way of simplifying code for Terraform configurations is by using pipeline templates to represent our infrastructure. Each value in the template parameters is used to customize our configuration which is then built dynamically during the build pipeline. We can go from the traditional repository structure of storing our Terraform configuration files in folders to representing our infrastructure with pipeline template parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hMt_d3OF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/uxvwmuyzhc8aiixapbvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hMt_d3OF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/uxvwmuyzhc8aiixapbvg.png" alt="TraditionalvsSimple"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Structuring our source code repositories this way allow us to scale our solution much easier and provides some of the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more one-off changes can be made to Terraform configuration files since all configurations are built dynamically during the build pipeline and will all be the same. This prevents issues where team members are making changes to one system's Terraform configuration causing us to have separate one-off configurations to maintain.&lt;/li&gt;
&lt;li&gt;We can now separate our Terraform code from the team that deploys the configurations. All the Terraform modules and files are stored in another repository which we can limit access to and allow just our Terraform team to manage them.&lt;/li&gt;
&lt;li&gt;Change tracking of infrastructure resources is much more simplified now since we are just looking at template parameter value changes in a single file.&lt;/li&gt;
&lt;li&gt;Each deployment is going to be more consistent since we are using the same base Terraform configurations in each deployment. This also allows us to treat our Terraform configurations as cattle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, we will use the Azure DevOps Demo Generator to import an Azure DevOps project. This project has been pre-configured to deploy Azure Container Instances with Terraform using pipeline templates as code. We will deploy some resources to our Azure subscription and review the innerworkings of this concept. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin this guide you'll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://azure.microsoft.com/en-us/"&gt;Azure Subscription&lt;/a&gt;, you can get started with a free account.&lt;/li&gt;
&lt;li&gt;An &lt;a href="http://dev.azure.com"&gt;Azure DevOps Organization&lt;/a&gt;. The basic plan is free for the first 5 users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Importing the Project with Azure DevOps Demo Generator
&lt;/h2&gt;

&lt;p&gt;First, we are going to import an Azure DevOps template project into our Azure DevOps organization. This will allow us to get started as fast as possible. To get started, navigate to the &lt;a href="https://azureDevOpsdemogenerator.azurewebsites.net/"&gt;Azure DevOps Demo Generator&lt;/a&gt; website. &lt;/p&gt;

&lt;p&gt;Sign in with your Azure DevOps account. Select &lt;strong&gt;Accept&lt;/strong&gt; to authorize the Azure DevOps Demo Generator application to access your account. Now we are ready to import our template. Select &lt;strong&gt;Choose Template&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bYaovvJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/8bl0ax3pwcjajd3b1dmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bYaovvJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/8bl0ax3pwcjajd3b1dmy.png" alt="ChooseTemplate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A pop-up window will appear, choose the &lt;strong&gt;Private&lt;/strong&gt; tab and select the &lt;strong&gt;GitHub&lt;/strong&gt; option. Paste in the following GitHub URL which hosts the template for our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://raw.githubusercontent.com/allanore/AzDevOpsYamlAsCode/master/TemplatesAsCode.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, select &lt;strong&gt;Submit&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8IS0oGwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/l843n5dgx26bu1pxugod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8IS0oGwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/l843n5dgx26bu1pxugod.png" alt="GitHubTemplate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select your organization from the drop-down menu and type in a name for the new project that we are creating. In the example I will be naming the project &lt;strong&gt;TemplateAsCode&lt;/strong&gt;. &lt;strong&gt;Check&lt;/strong&gt; the box for &lt;strong&gt;The extension(s) are offered to you...&lt;/strong&gt; and select &lt;strong&gt;Create Project&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--76ANenu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qtsq5ghkvb1zriuqkiqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--76ANenu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qtsq5ghkvb1zriuqkiqu.png" alt="CreateProject"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the &lt;strong&gt;Replace Tokens&lt;/strong&gt; and &lt;strong&gt;Terraform&lt;/strong&gt; &lt;em&gt;extensions&lt;/em&gt; are required for this project. Extensions are add-ons for Azure DevOps that provide an enhancement to the service. In this case we will be using the Replace Tokens extension to build our Terraform configuration files during the build pipeline. Additionally, we are using the Terraform extension as well to easily deploy our configurations to Azure. These two extensions will automatically be installed when we import the project via the Azure DevOps Demo Generator.&lt;/p&gt;

&lt;p&gt;Finally, after the import is successful, we will get the following message. Select &lt;strong&gt;Navigate to Project&lt;/strong&gt; to be directed straight to our new project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zGHsLHrs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vma5k07owwxe75i4alot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zGHsLHrs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vma5k07owwxe75i4alot.png" alt="SuccessfulImport"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next step we will review the innerworkings of the project that we just imported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Reviewing the Template as Code Design
&lt;/h2&gt;

&lt;p&gt;Let's look at how the code repositories for this project are set up. On the left-hand side select &lt;strong&gt;Repos&lt;/strong&gt; then choose &lt;strong&gt;Files&lt;/strong&gt;. By selecting the drop down, we can see that there are two source code repositories or &lt;em&gt;repos&lt;/em&gt; in our project. Select the &lt;strong&gt;ACI&lt;/strong&gt; repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zcz3tj-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2lffpc0tjlidwevkoyir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zcz3tj-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2lffpc0tjlidwevkoyir.png" alt="SelectRepo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ACI&lt;/strong&gt; repo for this project contains the infrastructure code for the Azure Container Instances in our Azure environment with folders for Development and Production. In this project, instead of creating a repository for each application, or one for Dev and Prod, we are creating a repository for each cloud service or component.  This allows for our infrastructure deployments to be simplified and as "cookie cutter" as possible which really shines with large environments. The environment size and business needs will really play a role on the most effective repo structure design. But, for this demonstration we are going to go this route to keep things simple.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pipelineconfig.yml&lt;/code&gt; is our build pipeline yaml file. If we look at the contents of this file, we can see it starts with the &lt;code&gt;resources&lt;/code&gt; section. This is sourcing the code from our 2nd repo, &lt;strong&gt;templates&lt;/strong&gt;, and allows us to use that repo code in our build pipeline. Next, is the &lt;code&gt;stages&lt;/code&gt; section which contains our &lt;code&gt;job&lt;/code&gt; and &lt;code&gt;tasks&lt;/code&gt; for building the Terraform configurations. In the &lt;code&gt;template&lt;/code&gt; section we are calling the template that we want to build which is pointing to the &lt;code&gt;aci-prod-sampleapp2.yml&lt;/code&gt; file. When we deploy this pipeline, it will deploy the components in that template file which we will look at next:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--94xCOTkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1y8wayojt10k0whqxfqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--94xCOTkP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1y8wayojt10k0whqxfqe.png" alt="ACI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The other three yaml files in the &lt;strong&gt;ACI&lt;/strong&gt; repo represent the Azure Container Instance infrastructure in our Production and Development environments. If we look at &lt;code&gt;aci-prod-sampleapp2.yml&lt;/code&gt;, we can see that it's a series of pipeline templates sourcing from the &lt;strong&gt;templates&lt;/strong&gt; repo. The template parameters are what is making up the configuration of our components, in this example we have two templates, one for the resource group and one for the actual ACI component. Breaking up the resource group and the ACI resource into separate templates allows us to deploy more than one ACI resource to a resource group: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6e-ZzGtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3bjk0c1t2jrmsi6w7l5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6e-ZzGtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/3bjk0c1t2jrmsi6w7l5b.png" alt="LiveInfrastructure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;strong&gt;templates&lt;/strong&gt; repo we can see our two yaml files for our pipeline templates along with a Terraform folder. This folder is where we keep our "templatized" terraform configuration files. These files contain generic Terraform configuration files with variables that are surrounded by a double "&lt;em&gt;". This tells our Replace Tokens task, which runs during the build pipeline, to replace any strings in our &lt;code&gt;container.tf&lt;/code&gt; and &lt;code&gt;main.tf&lt;/code&gt; files that are surrounded by "&lt;/em&gt;&lt;em&gt;" with it's respective environment variable. So `&lt;/em&gt;&lt;em&gt;CPU&lt;/em&gt;_` will be replaced by the CPU environment variable that we declare in the pipeline template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rDC688Gc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xzasvav5xosn1qvxz2fw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rDC688Gc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/xzasvav5xosn1qvxz2fw.png" alt="terraformtemplate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When looking at the &lt;code&gt;azure-aci-rg.yml&lt;/code&gt; template, we can see at the top we are listing our parameters and then setting those as environment variables in the &lt;code&gt;cmdline@2&lt;/code&gt; task. Next is the &lt;code&gt;CopyFiles@2&lt;/code&gt; task. We are copying the Terraform configuration template file, &lt;code&gt;main.tf&lt;/code&gt;, from the &lt;code&gt;\Terraform\ACI&lt;/code&gt; directory of our source control repo to our &lt;code&gt;$(Build.ArtifactStagingDirectory)&lt;/code&gt;. This is where we are building our Terraform files to produce as an &lt;em&gt;artifact&lt;/em&gt;. An artifact, from a developer perspective, typically contains the compiled binaries and libraries used to run an application. These application files are then deployed to an environment in the release pipeline. With our IaC build pipeline, our Terraform configuration files are the artifacts in this case; and we will be deploying them with the release pipeline. In the last task we run the &lt;code&gt;replacetokens@3&lt;/code&gt; task to swap out the variables surrounded by __ with our associated environment variable. This entire template allows us to take in parameter values and generate a Terraform configuration file from it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4seSvDnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/w8w3y8irxnjb8fvwgzlx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4seSvDnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/w8w3y8irxnjb8fvwgzlx.png" alt="templates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we look at the &lt;code&gt;azure-aci-container.yml&lt;/code&gt; template, we can see the same structure as &lt;code&gt;azure-aci-rg.yml&lt;/code&gt;. Since Azure Container Instances require many more values to create than a resource group, we have many more parameters declared at the beginning and in the &lt;code&gt;CmdLine@2&lt;/code&gt; task, where we declare our environment variables. The key difference in this template is the extra &lt;code&gt;CmdLine@2&lt;/code&gt; task at the end that renames the &lt;code&gt;container.tf&lt;/code&gt; file to the name of the ACI resource. This allows us to create additional ACI resources in the same Terraform configuration by providing a unique name to the configuration file, so we don't copy over any files:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KAYEIUAN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f3ypow4mzzcmkdebdka2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KAYEIUAN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f3ypow4mzzcmkdebdka2.png" alt="templates2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we've reviewed our two repos, let's wrap our head around the workflow in this build pipeline. Our &lt;code&gt;pipelineconfig.yml&lt;/code&gt; file is our build pipeline file, which references the desired infrastructure yml file to build such as &lt;code&gt;aci-prod-sampleapp1.yml&lt;/code&gt;. This file then points to several pipeline templates located in the &lt;strong&gt;templates&lt;/strong&gt; repository which each build out the Terraform configuration files according to the parameter values specified in &lt;code&gt;aci-prod-sampleapp1.yml&lt;/code&gt;. Finally, once our Terraform configuration files are built, they are published as an artifact which will then be used by our release pipeline to deploy to Azure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oVHjgSUa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/azofprwdnmf5rwo8r999.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oVHjgSUa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/azofprwdnmf5rwo8r999.png" alt="buildflowchart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next step we are going to deploy the Azure Container Instance infrastructure described in the &lt;code&gt;aci-prod-sampleapp2.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Deploying Resources
&lt;/h2&gt;

&lt;p&gt;First, let's run our build pipeline. On the left-hand side select &lt;strong&gt;Pipelines&lt;/strong&gt; to expand the options underneath it. Then, once again, select &lt;strong&gt;Pipelines&lt;/strong&gt; to see our build pipelines. Select &lt;strong&gt;Terraform-ACI-CI&lt;/strong&gt; and select &lt;strong&gt;Run pipeline&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TxAX0gNZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zz0dwpu6taww9e4eoj0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TxAX0gNZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zz0dwpu6taww9e4eoj0s.png" alt="runbuildpipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Run&lt;/strong&gt; in the pop-up window to start our build pipeline. It will run for a minute or so and the artifacts will then be generated for our sampleapp2 infrastructure. We can view these artifacts by selecting &lt;strong&gt;1 published&lt;/strong&gt; under the &lt;strong&gt;artifacts&lt;/strong&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NDnA3YZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dx4ulu5egv35o4wd9utn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NDnA3YZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dx4ulu5egv35o4wd9utn.png" alt="artifacts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see our Terraform configuration files for both ACI resources are there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3STSftX---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/u0aq58k7qgyum5qdaqbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3STSftX---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/u0aq58k7qgyum5qdaqbp.png" alt="terraformartifacts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's deploy the infrastructure to our Azure subscription by running the release pipeline, but first we need to edit the release pipeline to configure a connection to our Azure subscription. On the left hand side expand &lt;strong&gt;Pipelines&lt;/strong&gt; and select &lt;strong&gt;Releases&lt;/strong&gt;. We can see our &lt;strong&gt;Terraform-ACI-CD&lt;/strong&gt; pipeline has been imported, select &lt;strong&gt;Edit&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QShq4hC_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/mhh3puim3ispgctvu090.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QShq4hC_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/mhh3puim3ispgctvu090.png" alt="editreleasepipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under our &lt;strong&gt;Build&lt;/strong&gt; stage select &lt;strong&gt;1 job, 5 tasks&lt;/strong&gt; to edit our tasks to include our Azure subscription:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x4Qg8Q3n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zs53lifv1qlbyxbjckx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x4Qg8Q3n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/zs53lifv1qlbyxbjckx1.png" alt="editreleasetask"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the first task &lt;strong&gt;Set up Azure Storage Account...&lt;/strong&gt; and click on the drop-down box under &lt;strong&gt;Azure subscription&lt;/strong&gt;. A list of subscriptions associated with your tenant will appear in this box. Select one that you would like to deploy the example Azure Container Instances too and select &lt;strong&gt;Authorize&lt;/strong&gt;. You may be prompted to login to your Azure account. This process will create a Service Principal account in your Azure tenant and assign permissions to that subscription with that account. Azure DevOps will set this up as a service connection and use that to connect to Azure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CoHNzwfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/mq4jcli9b1y9em1x6d78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CoHNzwfM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/mq4jcli9b1y9em1x6d78.png" alt="addazuresubscription1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to configure the remaining Terraform tasks with the same Azure service connection. The new connection that we made should now show up in the drop-down menu under &lt;strong&gt;Available Azure service connections&lt;/strong&gt;. Select this for all 3 of the Terraform tasks that say &lt;strong&gt;some settings need attention&lt;/strong&gt; this is because they are missing their Azure subscription settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3mC777P9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/oiul4pk9tofhjtm925om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3mC777P9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/oiul4pk9tofhjtm925om.png" alt="addazuresubscription2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the &lt;strong&gt;Variables&lt;/strong&gt; tab at the top. We will need to rename the &lt;strong&gt;statestorageaccount&lt;/strong&gt; variable value. You can set this to anything you want. This because we are storing the Terraform state in an Azure storage account which is required to have a publicly unique name. If we don't rename this, we will get an error during the release pipeline deployment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sC_6UTwN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c7vrvbrfuo36j8ukx86o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sC_6UTwN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c7vrvbrfuo36j8ukx86o.png" alt="editvariables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once complete, select &lt;strong&gt;Save&lt;/strong&gt; at the top. Now we are ready to deploy our infrastructure. Select &lt;strong&gt;Create Release&lt;/strong&gt; and then select &lt;strong&gt;Create&lt;/strong&gt; to initiate the release pipeline. We will see a new message in green indicating that the release has been created. Select &lt;strong&gt;Release-1&lt;/strong&gt; to view the release process for deploying the ACI resources into our Azure subscription:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ovtq8bB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5s2hyvgmisa8mpk07jc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ovtq8bB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5s2hyvgmisa8mpk07jc7.png" alt="releasecreated"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will see that the release is running and can view the different steps of our release pipeline. This is a typical pipeline for deploying Terraform code, we provision a storage account to store our Terraform state, run a &lt;code&gt;terraform init&lt;/code&gt; to initialize our Terraform environment and connect to our remote state (azure storage account in this case). Then we run a &lt;code&gt;terraform plan&lt;/code&gt; to verify our configuration files have no issues. Finally if all the previous tasks are successful we run our &lt;code&gt;terraform apply -auto-approve&lt;/code&gt; to deploy the infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dLuOxWxa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/t6kem3f2iyids8hs55pu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dLuOxWxa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/t6kem3f2iyids8hs55pu.png" alt="releaserunning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once our release has run successfully, we will see a &lt;strong&gt;Succeeded&lt;/strong&gt; message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3f8Qxcrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qfftn9qtgbcno987yxju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3f8Qxcrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/qfftn9qtgbcno987yxju.png" alt="releasesucceeded"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we look in our Azure portal, we can see the resources are in fact there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6PWyShwa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kehrbuxfxbrw0nuyxobo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6PWyShwa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kehrbuxfxbrw0nuyxobo.png" alt="azureresources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we will add another ACI resource to our sampleapp2 application and redeploy our configuration with a pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Modifying Resources with Pull Request
&lt;/h2&gt;

&lt;p&gt;We need to set up a branch policy for our &lt;strong&gt;master&lt;/strong&gt; branch, this will allow us to automatically kick off a build if a Pull Request is initiated. To do this expand &lt;strong&gt;Repos&lt;/strong&gt; and select &lt;strong&gt;Branches&lt;/strong&gt;. On the &lt;strong&gt;master&lt;/strong&gt; branch select the &lt;strong&gt;...&lt;/strong&gt; all the way on the right side and select &lt;strong&gt;Branch policies&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qOVmuaDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/girimkcjmw88g5quhti8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qOVmuaDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/girimkcjmw88g5quhti8.png" alt="selectbranches"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;+ Add build policy&lt;/strong&gt;. In the pop-up window, select our build pipeline &lt;strong&gt;Terraform-ACI-CI&lt;/strong&gt; and keep the defaults for everything else. Select &lt;strong&gt;Save&lt;/strong&gt; to create the build policy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ppzuJpAO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/j5l41gi19msenz5e6hz6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ppzuJpAO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/j5l41gi19msenz5e6hz6.png" alt="branchpolicy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are ready to submit a Pull Request and trigger a new build. Let's create a new branch to make our changes in. In the &lt;strong&gt;ACI&lt;/strong&gt; repository select the dropdown labeled &lt;strong&gt;master&lt;/strong&gt; and select &lt;strong&gt;+ New Branch&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OUDeIQ0_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0eep8g4yu7sfwz4jdzis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OUDeIQ0_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0eep8g4yu7sfwz4jdzis.png" alt="newbranch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our example we will name the new branch &lt;strong&gt;deploysampleapp2&lt;/strong&gt;. Select &lt;strong&gt;Create&lt;/strong&gt;. We are now actively using the new branch we just created in Azure DevOps. Select the &lt;code&gt;aci-prod-sampleapp2.yml&lt;/code&gt; file and select &lt;strong&gt;Edit&lt;/strong&gt; to edit the file. We are going to add another ACI resource by adding in another ACI template with the required parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Build Azure Container Group
- template: azure-aci-container.yml@templates
  parameters:
    ContainerGroupName: 'sampleapp2-3'
    DNSName: 'cloudskillssampleapp2-3'
    OS: 'Linux'
    ContainerName: 'helloworld'
    Image: 'microsoft/aci-helloworld:latest'
    CPU: '1'
    Memory: '4'
    Port: '80'
    Protocol: 'TCP'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, select &lt;strong&gt;Commit&lt;/strong&gt; to save our changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--scp9dWID--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ggjavbz7yebh2yl5hs4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--scp9dWID--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ggjavbz7yebh2yl5hs4u.png" alt="editpipelineconfig"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Commit&lt;/strong&gt; again. Now there will be a pop up for a pull request, select &lt;strong&gt;Create a pull request&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--grJr5pxO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7nlucad5cjdcav2il47g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--grJr5pxO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7nlucad5cjdcav2il47g.png" alt="pullrequest"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next window we can write in some information on our pull request and description. This provides great documentation for our deployments:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TAy5Nkn1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/s1ouy9xbh4fybp49xv5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TAy5Nkn1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/s1ouy9xbh4fybp49xv5d.png" alt="pullrequestdescription"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Create&lt;/strong&gt;, and in the next windows select &lt;strong&gt;Set auto-complete&lt;/strong&gt;. This will complete the Pull Request if our build runs successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BbMY_xnE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7yrk5ku955kdqz7j06xl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BbMY_xnE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7yrk5ku955kdqz7j06xl.png" alt="pullrequestrunning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then select &lt;strong&gt;set auto-complete&lt;/strong&gt; again to confirm. Our build pipeline is now running with the changes from our new branch. If the build is successful, our branch will merge with the master branch with our new changes. Also note, if we look back at our pull request history, we can see a very simple outline of the new infrastructure that was added. This is one of the benefits of using the template parameters to define our infrastructure. Our pull requests get much easier to review:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Opf9uOi---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i55z1pbban67s0v2w8xo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Opf9uOi---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i55z1pbban67s0v2w8xo.png" alt="simplepullrequest"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's go ahead and deploy our new resources. Expand &lt;strong&gt;Pipelines&lt;/strong&gt; on the left-hand side and select &lt;strong&gt;Releases&lt;/strong&gt;. Then select the &lt;strong&gt;Terraform-ACI-CD&lt;/strong&gt; pipelines and select &lt;strong&gt;Create Release&lt;/strong&gt;. Select &lt;strong&gt;Create&lt;/strong&gt; to start deploying.&lt;/p&gt;

&lt;p&gt;In our second release our additional ACI resource will be deployed. Once the release has been completed, we can double check in the Azure portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bQWwysNq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v2omwh8e1ykepmqq6yv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bQWwysNq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v2omwh8e1ykepmqq6yv4.png" alt="azureresources2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article we imported a project that utilizes pipeline templates to generate Terraform configurations during the build pipeline. We also successfully deployed Azure Container Instance resources from these pipeline templates and even added additional resources using a Pull Request.&lt;/p&gt;

&lt;p&gt;As you can see, this model can greatly simplify Infrastructure as Code environments and provide greater management and consistency. However, keep in mind that this strategy might not fit all scenarios. For example, a tiny environment might not need to go this far with only a few resources. They may be fine with just a few repositories with Terraform configurations stored inside them. Also, an extremely complex environment could be too limited by the templatized configurations and may require a much more complex set up. &lt;/p&gt;

&lt;p&gt;The Infrastructure as Code model can become difficult to manage at large scale, using pipeline templates instead of treating our Terraform config files like sheep prevents us from creating snowflake infrastructure and allows us to scale our infrastructure rapidly and in a stable way. For more information on Azure DevOps templates be sure to check out &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops"&gt;Microsoft's documentation&lt;/a&gt;. Also, if you're interested in learning more about Terraform take a look at their &lt;a href="https://www.terraform.io/intro/index.html"&gt;website&lt;/a&gt; for more material. &lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
