When 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:
- Download terraform for windows
- Copy files from the zip to c:\terraform
- 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.
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.
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.
LikeLike
The task is open source, you can find the source code at github: https://github.com/XpiritBV/Xpirit-Vsts-Release-Terraform
LikeLike
What are the powershell files in your Terrafor task repo for? I am new to DevOps and would love to know more about building new custom tasks. Are you using it instead of NPM as described here https://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=vsts
LikeLike
Any plans to update the tasks to use the az module as opposed to azurerm as it fails if azurerm is replaced…
LikeLiked by 1 person
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.
LikeLike
Remove it from your template will do the trick
LikeLike
any other solution to “destroy” ? , that not be “Remove it from your template will do the trick”.
Regards
LikeLike
Removing them from the template would be managing your resources as iaas. You can use the Azure CLI/ powershell/ portal to remove resources.
LikeLike
Hello Peter, thanks for this DevOps extension. Is is possible to fix the bug with Import command? thanks.
LikeLike
Hi Amin,
I would like to help you, but I do not have the time at the moment to create and test a fix. I’ll accept a pr to fix it. I have currently no time to create a fix. You can find the source on github: https://github.com/XpiritBV/Xpirit-Vsts-Release-Terraform
LikeLike
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.
LikeLike
Are you sure terraforms in installed. I have not used the extension for a few years, so I can not verify the output you see.
LikeLike