Getting started with Terraform on Windows and Azure

Quick guide to deploy “hello world” with Terraform on AzureRM

small-a17be924When reviewing possible for creating declarative infrastructure, I looked at Terraform. In this blog post I show how easy it is to get started and create AzureRM resources with Terraform.

Installing Terraform

To get started with Terraform on windows:

  1. Download terraform for windows
  2. Copy files from the zip to c:\terraform
  3. Add the terraform binaries in your path by running:
set PATH=%PATH%;C:\terraform

To connect to AzureRM API for deployment you need a Service Principle. The Terraform azurerm provider looks like:

provider "azurerm" {
  subscription_id = "..."
  client_id       = "..."
  client_secret   = "..."
  tenant_id       = "..."
}

Follow the guide for creating a Service Principle for AzureRM deployment.

Now you are ready to start using Terraform with AzureRM.

Build your first infrastructure

Make an empty directory from your project: c:\t\TerraformT. Then create a file with your Terraform helloworld.tf template to create your first resourcegroup:

provider "azurerm" {
subscription_id = "..."
client_id       = "..."
client_secret   = "..."
tenant_id       = "..."
}

variable "location" { default = "West Europe" }

resource "azurerm_resource_group" "test" {
name     = "HelloWorld"
location = "${var.location}"
}

Next create an initial terraform plan, run: terraform plan

Refreshing Terraform state prior to plan...

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ azurerm_resource_group.test
location: "" => "westeurope"
name:     "" => "HelloWorld"

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

Next you can apply the plan to Azure, run: terraform apply

azurerm_resource_group.test: Creating...
  location: "" => "westeurope"
  name:     "" => "HelloWorld"
azurerm_resource_group.test: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Now we have created our first azurerm_resource_group. Next we can expand the resource group with more azure infrastructure. Lets add a VM with Windows Datacenter to the deployment:

# Configure the Azure Resource Manager Provider
provider "azurerm" {
  subscription_id = "..."
  client_id       = "..."
  client_secret   = "..."
  tenant_id       = "..."
}

variable "location" { default = "West Europe" }
variable "username" { default = "adminuser" }
variable "password" { default = "Pass654321" }

resource "azurerm_resource_group" "test" {
  name     = "HelloWorld"
  location = "${var.location}"
}

resource "azurerm_virtual_network" "test" {
  name                = "test"
  address_space       = ["10.0.0.0/16"]
  location            = "${var.location}"
  resource_group_name = "${azurerm_resource_group.test.name}"
}

resource "azurerm_subnet" "test" {
  name                 = "test"
  resource_group_name  = "${azurerm_resource_group.test.name}"
  virtual_network_name = "${azurerm_virtual_network.test.name}"
  address_prefix       = "10.0.2.0/24"
}

resource "azurerm_network_interface" "test" {
  name                = "test"
  location            = "${var.location}"
  resource_group_name = "${azurerm_resource_group.test.name}"
  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = "${azurerm_subnet.test.id}"
    private_ip_address_allocation = "dynamic"
  }
}

resource "azurerm_storage_account" "test" {
  name                = "helloworld25662"
  resource_group_name = "${azurerm_resource_group.test.name}"
  location            = "${var.location}"
  account_type        = "Standard_LRS"
}

resource "azurerm_storage_container" "test" {
  name                  = "helloworld"
  resource_group_name   = "${azurerm_resource_group.test.name}"
  storage_account_name  = "${azurerm_storage_account.test.name}"
  container_access_type = "private"
}

resource "azurerm_virtual_machine" "test" {
  name                  = "helloworld"
  location              = "${var.location}"
  resource_group_name   = "${azurerm_resource_group.test.name}"
  network_interface_ids = ["${azurerm_network_interface.test.id}"]
  vm_size               = "Standard_A0"

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2012-R2-Datacenter"
    version   = "latest"
  }

  storage_os_disk {
    name          = "myosdisk1"
    vhd_uri       = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd"
    caching       = "ReadWrite"
    create_option = "FromImage"
  }

  os_profile {
    computer_name  = "helloworld"
    admin_username = "${var.username}"
    admin_password = "${var.password}"
  }
}

Run: terraform plan

Refreshing Terraform state prior to plan...

azurerm_resource_group.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ azurerm_network_interface.test
    applied_dns_servers.#:                                                  "" => "<computed>"
    dns_servers.#:                                                          "" => "<computed>"
    internal_dns_name_label:                                                "" => "<computed>"
    internal_fqdn:                                                          "" => "<computed>"
    ip_configuration.#:                                                     "" => "1"
    ip_configuration.~1070970412.load_balancer_backend_address_pools_ids.#: "" => "<computed>"
    ip_configuration.~1070970412.load_balancer_inbound_nat_rules_ids.#:     "" => "<computed>"
    ip_configuration.~1070970412.name:                                      "" => "testconfiguration1"
    ip_configuration.~1070970412.private_ip_address:                        "" => "<computed>"
    ip_configuration.~1070970412.private_ip_address_allocation:             "" => "dynamic"
    ip_configuration.~1070970412.subnet_id:                                 "" => "${azurerm_subnet.test.id}"
    location:                                                               "" => "westeurope"
    mac_address:                                                            "" => "<computed>"
    name:                                                                   "" => "test"
    network_security_group_id:                                              "" => "<computed>"
    private_ip_address:                                                     "" => "<computed>"
    resource_group_name:                                                    "" => "HelloWorld"
    virtual_machine_id:                                                     "" => "<computed>"

+ azurerm_storage_account.test
    account_type:             "" => "Standard_LRS"
    location:                 "" => "westeurope"
    name:                     "" => "helloworld25662"
    primary_blob_endpoint:    "" => "<computed>"
    primary_file_endpoint:    "" => "<computed>"
    primary_location:         "" => "<computed>"
    primary_queue_endpoint:   "" => "<computed>"
    primary_table_endpoint:   "" => "<computed>"
    resource_group_name:      "" => "HelloWorld"
    secondary_blob_endpoint:  "" => "<computed>"
    secondary_location:       "" => "<computed>"
    secondary_queue_endpoint: "" => "<computed>"
    secondary_table_endpoint: "" => "<computed>"

+ azurerm_storage_container.test
    container_access_type: "" => "private"
    name:                  "" => "helloworld"
    properties.#:          "" => "<computed>"
    resource_group_name:   "" => "HelloWorld"
    storage_account_name:  "" => "helloworld25662"

+ azurerm_subnet.test
    address_prefix:            "" => "10.0.2.0/24"
    ip_configurations.#:       "" => "<computed>"
    name:                      "" => "test"
    network_security_group_id: "" => "<computed>"
    resource_group_name:       "" => "HelloWorld"
    route_table_id:            "" => "<computed>"
    virtual_network_name:      "" => "test"

+ azurerm_virtual_machine.test
    availability_set_id:                         "" => "<computed>"
    license_type:                                "" => "<computed>"
    location:                                    "" => "westeurope"
    name:                                        "" => "helloworld"
    network_interface_ids.#:                     "" => "<computed>"
    os_profile.#:                                "" => "1"
    os_profile.522345080.admin_password:         "" => "Pass654321"
    os_profile.522345080.admin_username:         "" => "adminuser"
    os_profile.522345080.computer_name:          "" => "helloworld"
    os_profile.522345080.custom_data:            "" => "<computed>"
    plan.#:                                      "" => "<computed>"
    resource_group_name:                         "" => "HelloWorld"
    storage_image_reference.#:                   "" => "1"
    storage_image_reference.103127886.offer:     "" => "WindowsServer"
    storage_image_reference.103127886.publisher: "" => "MicrosoftWindowsServer"
    storage_image_reference.103127886.sku:       "" => "2012-R2-Datacenter"
    storage_image_reference.103127886.version:   "" => "latest"
    storage_os_disk.#:                           "" => "1"
    storage_os_disk.~808156298.caching:          "" => "ReadWrite"
    storage_os_disk.~808156298.create_option:    "" => "FromImage"
    storage_os_disk.~808156298.image_uri:        "" => ""
    storage_os_disk.~808156298.name:             "" => "myosdisk1"
    storage_os_disk.~808156298.os_type:          "" => ""
    storage_os_disk.~808156298.vhd_uri:          "" => "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd"
    vm_size:                                     "" => "Standard_A0"

+ azurerm_virtual_network.test
    address_space.#:     "" => "1"
    address_space.0:     "" => "10.0.0.0/16"
    location:            "" => "westeurope"
    name:                "" => "test"
    resource_group_name: "" => "HelloWorld"
    subnet.#:            "" => "<computed>"

Plan: 6 to add, 0 to change, 0 to destroy.
And then run: terraform apply
azurerm_resource_group.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld)
azurerm_storage_account.test: Creating...
  account_type:             "" => "Standard_LRS"
  location:                 "" => "westeurope"
  name:                     "" => "helloworld25662"
  primary_blob_endpoint:    "" => "<computed>"
  primary_file_endpoint:    "" => "<computed>"
  primary_location:         "" => "<computed>"
  primary_queue_endpoint:   "" => "<computed>"
  primary_table_endpoint:   "" => "<computed>"
  resource_group_name:      "" => "HelloWorld"
  secondary_blob_endpoint:  "" => "<computed>"
  secondary_location:       "" => "<computed>"
  secondary_queue_endpoint: "" => "<computed>"
  secondary_table_endpoint: "" => "<computed>"
azurerm_virtual_network.test: Creating...
  address_space.#:     "" => "1"
  address_space.0:     "" => "10.0.0.0/16"
  location:            "" => "westeurope"
  name:                "" => "test"
  resource_group_name: "" => "HelloWorld"
  subnet.#:            "" => "<computed>"
azurerm_virtual_network.test: Creation complete
azurerm_subnet.test: Creating...
  address_prefix:            "" => "10.0.2.0/24"
  ip_configurations.#:       "" => "<computed>"
  name:                      "" => "test"
  network_security_group_id: "" => "<computed>"
  resource_group_name:       "" => "HelloWorld"
  route_table_id:            "" => "<computed>"
  virtual_network_name:      "" => "test"
azurerm_subnet.test: Creation complete
azurerm_network_interface.test: Creating...
  applied_dns_servers.#:                                                 "" => "<computed>"
  dns_servers.#:                                                         "" => "<computed>"
  internal_dns_name_label:                                               "" => "<computed>"
  internal_fqdn:                                                         "" => "<computed>"
  ip_configuration.#:                                                    "" => "1"
  ip_configuration.1430870016.load_balancer_backend_address_pools_ids.#: "" => "<computed>"
  ip_configuration.1430870016.load_balancer_inbound_nat_rules_ids.#:     "" => "<computed>"
  ip_configuration.1430870016.name:                                      "" => "testconfiguration1"
  ip_configuration.1430870016.private_ip_address:                        "" => "<computed>"
  ip_configuration.1430870016.private_ip_address_allocation:             "" => "dynamic"
  ip_configuration.1430870016.subnet_id:                                 "" => "/subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Network/virtualNetworks/test/subnets/test"
  location:                                                              "" => "westeurope"
  mac_address:                                                           "" => "<computed>"
  name:                                                                  "" => "test"
  network_security_group_id:                                             "" => "<computed>"
  private_ip_address:                                                    "" => "<computed>"
  resource_group_name:                                                   "" => "HelloWorld"
  virtual_machine_id:                                                    "" => "<computed>"
azurerm_network_interface.test: Creation complete
azurerm_storage_account.test: Still creating... (10s elapsed)
azurerm_storage_account.test: Still creating... (20s elapsed)
azurerm_storage_account.test: Creation complete
azurerm_storage_container.test: Creating...
  container_access_type: "" => "private"
  name:                  "" => "helloworld"
  properties.#:          "" => "<computed>"
  resource_group_name:   "" => "HelloWorld"
  storage_account_name:  "" => "helloworld25662"
azurerm_storage_container.test: Creation complete
azurerm_virtual_machine.test: Creating...
  availability_set_id:                         "" => "<computed>"
  license_type:                                "" => "<computed>"
  location:                                    "" => "westeurope"
  name:                                        "" => "helloworld"
  network_interface_ids.#:                     "" => "1"
  network_interface_ids.214586890:             "" => "/subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Network/networkInterfaces/test"
  os_profile.#:                                "" => "1"
  os_profile.522345080.admin_password:         "" => "Pass654321"
  os_profile.522345080.admin_username:         "" => "adminuser"
  os_profile.522345080.computer_name:          "" => "helloworld"
  os_profile.522345080.custom_data:            "" => "<computed>"
  plan.#:                                      "" => "<computed>"
  resource_group_name:                         "" => "HelloWorld"
  storage_image_reference.#:                   "" => "1"
  storage_image_reference.103127886.offer:     "" => "WindowsServer"
  storage_image_reference.103127886.publisher: "" => "MicrosoftWindowsServer"
  storage_image_reference.103127886.sku:       "" => "2012-R2-Datacenter"
  storage_image_reference.103127886.version:   "" => "latest"
  storage_os_disk.#:                           "" => "1"
  storage_os_disk.1235954129.caching:          "" => "ReadWrite"
  storage_os_disk.1235954129.create_option:    "" => "FromImage"
  storage_os_disk.1235954129.image_uri:        "" => ""
  storage_os_disk.1235954129.name:             "" => "myosdisk1"
  storage_os_disk.1235954129.os_type:          "" => ""
  storage_os_disk.1235954129.vhd_uri:          "" => "https://helloworld25662.blob.core.windows.net/helloworld/myosdisk1.vhd"
  vm_size:                                     "" => "Standard_A0"
azurerm_virtual_machine.test: Still creating... (10s elapsed)
azurerm_virtual_machine.test: Still creating... (20s elapsed)
azurerm_virtual_machine.test: Still creating... (30s elapsed)
azurerm_virtual_machine.test: Still creating... (40s elapsed)
azurerm_virtual_machine.test: Still creating... (50s elapsed)
azurerm_virtual_machine.test: Still creating... (1m0s elapsed)
azurerm_virtual_machine.test: Still creating... (1m10s elapsed)
azurerm_virtual_machine.test: Still creating... (1m20s elapsed)
azurerm_virtual_machine.test: Still creating... (1m30s elapsed)
azurerm_virtual_machine.test: Still creating... (1m40s elapsed)
azurerm_virtual_machine.test: Still creating... (1m50s elapsed)
azurerm_virtual_machine.test: Still creating... (2m0s elapsed)
azurerm_virtual_machine.test: Still creating... (2m10s elapsed)
azurerm_virtual_machine.test: Still creating... (2m20s elapsed)
azurerm_virtual_machine.test: Still creating... (2m30s elapsed)
azurerm_virtual_machine.test: Still creating... (2m40s elapsed)
azurerm_virtual_machine.test: Still creating... (2m50s elapsed)
azurerm_virtual_machine.test: Still creating... (3m0s elapsed)
azurerm_virtual_machine.test: Still creating... (3m10s elapsed)
azurerm_virtual_machine.test: Still creating... (3m20s elapsed)
azurerm_virtual_machine.test: Still creating... (3m30s elapsed)
azurerm_virtual_machine.test: Still creating... (3m40s elapsed)
azurerm_virtual_machine.test: Still creating... (3m50s elapsed)
azurerm_virtual_machine.test: Still creating... (4m0s elapsed)
azurerm_virtual_machine.test: Still creating... (4m10s elapsed)
azurerm_virtual_machine.test: Still creating... (4m20s elapsed)
azurerm_virtual_machine.test: Still creating... (4m30s elapsed)
azurerm_virtual_machine.test: Still creating... (4m40s elapsed)
azurerm_virtual_machine.test: Still creating... (4m50s elapsed)
azurerm_virtual_machine.test: Still creating... (5m0s elapsed)
azurerm_virtual_machine.test: Still creating... (5m10s elapsed)
azurerm_virtual_machine.test: Still creating... (5m20s elapsed)
azurerm_virtual_machine.test: Still creating... (5m30s elapsed)
azurerm_virtual_machine.test: Still creating... (5m40s elapsed)
azurerm_virtual_machine.test: Still creating... (5m50s elapsed)
azurerm_virtual_machine.test: Still creating... (6m0s elapsed)
azurerm_virtual_machine.test: Still creating... (6m10s elapsed)
azurerm_virtual_machine.test: Still creating... (6m20s elapsed)
azurerm_virtual_machine.test: Still creating... (6m30s elapsed)
azurerm_virtual_machine.test: Still creating... (6m40s elapsed)
azurerm_virtual_machine.test: Creation complete

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

The last step is to complete the application life cycle by removing your resources, do: terraform destroy

Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

azurerm_resource_group.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld)
azurerm_virtual_network.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Network/virtualNetworks/test)
azurerm_storage_account.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/helloworld/providers/Microsoft.Storage/storageAccounts/helloworld25662)
azurerm_storage_container.test: Refreshing state... (ID: helloworld)
azurerm_subnet.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Network/virtualNetworks/test/subnets/test)
azurerm_network_interface.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Network/networkInterfaces/test)
azurerm_virtual_machine.test: Refreshing state... (ID: /subscriptions/9bdde4e2-946e-4961-b111-fadcea94577d/resourceGroups/HelloWorld/providers/Microsoft.Compute/virtualMachines/helloworld)
azurerm_virtual_machine.test: Destroying...
azurerm_virtual_machine.test: Still destroying... (10s elapsed)
azurerm_virtual_machine.test: Still destroying... (20s elapsed)
azurerm_virtual_machine.test: Still destroying... (30s elapsed)
azurerm_virtual_machine.test: Still destroying... (40s elapsed)
azurerm_virtual_machine.test: Still destroying... (50s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m0s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m10s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m20s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m30s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m40s elapsed)
azurerm_virtual_machine.test: Still destroying... (1m50s elapsed)
azurerm_virtual_machine.test: Still destroying... (2m0s elapsed)
azurerm_virtual_machine.test: Destruction complete
azurerm_network_interface.test: Destroying...
azurerm_storage_container.test: Destroying...
azurerm_network_interface.test: Destruction complete
azurerm_subnet.test: Destroying...
azurerm_storage_container.test: Destruction complete
azurerm_storage_account.test: Destroying...
azurerm_storage_account.test: Destruction complete
azurerm_subnet.test: Still destroying... (10s elapsed)
azurerm_subnet.test: Destruction complete
azurerm_virtual_network.test: Destroying...
azurerm_virtual_network.test: Still destroying... (10s elapsed)
azurerm_virtual_network.test: Destruction complete
azurerm_resource_group.test: Destroying...
azurerm_resource_group.test: Still destroying... (10s elapsed)
azurerm_resource_group.test: Still destroying... (20s elapsed)
azurerm_resource_group.test: Still destroying... (30s elapsed)
azurerm_resource_group.test: Still destroying... (40s elapsed)
azurerm_resource_group.test: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 7 destroyed.

Now you have seen creating a Terraform template, and updating the template with Terraform on AzureRM. More information on what resources are available from the azurerm provider, check the Terraform documentation.

12 thoughts on “Getting started with Terraform on Windows and Azure”

  1. I like you VSTS Custom Build Task. I couldn’t find a post on your blog about it. I like that it can use the Azure Service Principal Endpoint. I’m interested in how you wrote the VSTS Custom Build Task and how to integrate with this feature, I’m guessing you can call some VSTS PowerShell module commands.

    Like

  2. How about “destroy” infra by using this? thx. I’ve been using it to build infra very good! to erase it all I have not the same luck.

    Like

  3. Hello Peter, thanks for this DevOps extension. Is is possible to fix the bug with Import command? thanks.

    Like

  4. Hi Peter – I’m trying to use the “Run Terraform” in my Azure release pipeline. However, it fails at init stage with a strange error –
    2020-07-13T19:38:29.1206285Z ##[command]”terraform” init -input=false -no-color
    2020-07-13T19:38:29.4938837Z ##[error]The term ‘terraform’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Any help is much appreciated.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.