Terraform workspace specific lines of code in Azure - azure

I'm having an issue using terraform workspaces between a region pair. One workspace for South Central US and one for North Central US. Everything works great until it comes to zones. Public IP for example. South I’m setting zones, North won’t accept zone configuration empty, 0, or 1,2, or 3 because it doesn’t support zones yet. Hoping to set zones in SCU and eventually doing the same in NCU when they become available.
How do I use the same code in both regions? I know I can use workspace vars for values, but in this case it is an entire line of code. Seems like there should be an easy answer I’m just not thinking of.
Public IP code as an example below, but it the solution I would apply for VM deployments as well.
resource "azurerm_public_ip" "example" {
name = "acceptanceTestPublicIp1"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
allocation_method = "Static"
zones = [1]
tags = {
environment = "Production"
}
}

You can check the terraform.workspace variable, and set the value to null if you don't want to set it to anything in a specific workspace:
zones = terraform.workspace == "south" ? [1] : null
Change "south" to whatever the name of your Terraform workspace is that needs to set the zones value.

#Mark B, you're brilliant! Such a simple answer that works. Bravo!
Here's an example of my variables.tfvars
firewalla-AvailabilityZone = {
hub-ncu = null
hub-scu = [1]
}
firewallb-AvailabilityZone = {
hub-ncu = null
hub-scu = [3]
}
Then in the resource:
zones = var.firewalla-AvailabilityZone[terraform.workspace]

Related

Terraform how to skip argument for 1 workspace

Is it possible to skip 1 argument for 1 workspace in terraform?
resource "azurerm_application_gateway" "appgw" {
name = var.appgw
resource_group_name = var.resource_group
location = var.location
**zones = var.aks_zones**
sku {
name = var.app_gateway_sku
tier = var.app_gateway_tier
}
I am setting a DR environment in a region where availability zones are not supported, so for the script to pass the "Zones" argument needs to be skipped for one workspace only. Is this possible?
To fix this, I added an availability_zone = "No-Zone" argument to my AppGW IP address block.
Since the azurerm_application_gateway resource's zones variable is Optional (source), you can set the default value for your aks_zones variable to null:
variable "aks_zones" {
default = null
}
This way, you can skip specifying the aks_zones variable for your one workspace while setting a value for the other workspaces.

Get a list of possible outbound ip addresses in terraform

I'm trying to use the export from a azure function app in terraform to get the possible outbound ip addresses that I can add to a whitelist for a firewall
The parameter returned is a string of ips comma separated.
I have tried using the split function within terraform but it doesn't give a list, it gives an interface which can't be used as a list. I've tried using local scopes to add square brackets around it but still the same.
Let me just add this is terraform 11 not 12.
resource "azurerm_key_vault" "keyvault" {
name = "${var.project_name}-${var.environment}-kv"
location = "${azurerm_resource_group.environment.location}"
resource_group_name = "${azurerm_resource_group.environment.name}"
enabled_for_disk_encryption = true
tenant_id = "${var.tenant_id}"
sku_name = "standard"
network_acls {
bypass = "AzureServices"
default_action = "Deny"
ip_rules = "${split(",", azurerm_function_app.function.possible_outbound_ip_addresses)}"
}
tags = {
asset-code = "${var.storage_tags["asset_code"]}"
module-code = "${var.storage_tags["module_code"]}"
environment = "${var.environment}"
instance-code = "${var.storage_tags["instance_code"]}"
source = "terraform"
}
}
This comes back with the error "ip_rules must be a list".
Thanks
I think what you are seeing here is a classic Terraform 0.11 design flaw: when a value is unknown at plan time (because it will be decided only during apply), Terraform 0.11 can't properly track the type information for it.
Because possible_outbound_ip_addresses is an unknown value at planning time, the result of split with that string is also unknown. Because Terraform doesn't track type information for that result, the provider SDK code rejects that unknown value because it isn't a list.
To address this in Terraform 0.11 requires doing your initial run with the -target argument so that Terraform can focus on creating the function (and thus allocating its outbound IP addresses) first, and then deal with the processing of that string separately once it's known:
terraform apply -target=azurerm_function_app.function
terraform apply # to complete the rest of the work that -target excluded
Terraform 0.12 addressed this limitation by tracking type information for both known and unknown values, so in Terraform 0.12 the split function would see that you gave it an unknown string and accept that as being correctly typed, and then it would return an unknown list of strings to serve as a placeholder for the result that will be finally determined during the apply phase.
If is var.string is 1.2.3.4,5.6.7.8-
split(',', var.string)[0] should give you back 1.2.3.4 as a string. Your questions is difficult without an example.
Here is an example of how I can get a list of possible IPs
create a data source and then a locals var
app_services = [ "app1", "app2", "app3" ]
data "azurerm_app_service" "outbound_ips" {
count = length(var.app_services)
name = var.app_services[count.index]
resource_group_name = var.server_resource_group_name
}
locals {
apps_outbound_ips = distinct(flatten(concat(data.azurerm_app_service.outbound_ips.*.possible_outbound_ip_address_list)))
}
You don't have to use a data source either, if you are building the resource just use the outputs instead of a data source, in my case I use a data source as I build my apps separately.
Works flawlessly for me and produces a list of strings (Strings being each unique outbound IP of the set of app services / function apps) in the form of local.apps_outbound_ips
Enjoy :)

Creating two VMs in the same resource group without Terraform wanting to destoy the first one

I'm trying to deploy two virtual machines within the same resource group to our Azure platform with Terraform. After successfully creating the first one Terraform then wants to destroy the first one to create the second one after I've changed the second VM name and Azure tag.
I've been following the Terraform guide: https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html
resource "azurerm_virtual_machine" "main" {
location = "${var.location}"
name = "${var.vm_name}"
network_interface_ids = ["${azurerm_network_interface.main.id}"]
resource_group_name = "${var.resourcegroup_vm}"
vm_size = "${var.vm_size}"
tags {
application = "${var.tag}"
}
I expected Terraform to just create the second VM after changing its variable name and tag. Not wanting to destory the first one because of the name and tag change.
Terraform is based on HCL (Hashicorp Configuration Language), which is the format *.tf files are written in. It is a declarative language (as opposed to imperative), which means that you describe the desired state you want your infrastructure to be and Terraform will figure out what changes are needed to take it to that point.
If you first create an instance and then change its name you are telling Terraform that you no longer want your instance to have the old name but the new one.
To deploy a number of instances you can use the count attribute. You could then use interpolation to get names and tags based in the counter, something similar to this:
resource "azurerm_virtual_machine" "main" {
location = "${var.location}"
name = "${var.vm_name}-${count.index + 1}"
network_interface_ids = ["${azurerm_network_interface.main.id}"]
resource_group_name = "${var.resourcegroup_vm}"
vm_size = "${var.vm_size}"
tags {
application = "${var.tag}-${count.index + 1}"
}
count = 2
}
Note the attached -${count.index + 1} to name and the application tag.

Terraform retrieve CIDR/Prefix from existing VNETs/subnets

In Terraform, I want to create a route table for an existing subnet. To achieve the desired end result, I need to pull the CIDR/Prefix for the VNET. The VNET CIDR value is not known beforehand, the only values I know before launch is the VNET's name and Resource Group.
I would like to take the VNET CIDR/Prefix and insert it as a destination in the route table.
data "azurerm_virtual_network" "vnet" {
name = "${var.vnet_name}"
resource_group_name = "${var.vnet_rg}"
}
module "routetable" {
source = "modules/routetable"
route_table_name = "${var.route_table_name}"
resource_group_name =
"${data.azurerm_resource_group.vnet.name}"
location = "eastus"
route_prefixes = ["0.0.0.0/0", "${EXISTING_VNET_CIDR_HERE}"]
route_nexthop_types = ["VirtualAppliance", "VirtualAppliance"]
route_names = ["route1", "route2"]
}
just use data you are getting from the vnet:
${data.azurerm_virtual_network.vnet.address_spaces}
the only issue - assress_spaces is an array (i think its called list in terraforms terms).

How to approach repeatable items in Terraform

Say that I need to provision a large number of vpc subnets in terraform. Each subnet has a cidr, a name and a availability zone. So in other config management tools I'd do something like:
[
{
"name":"subnet1",
"cidr":"10.0.0.1/24",
"az":"us-west-1a"
},
{
"name":"subnet2",
"cidr":"10.0.0.2/24",
"az":"us-west-1b"
}
]
And then iterate over that array.
Terraform doesn't have a notion of arrays/objects as far as I can see. So, for arrays of single attributes I would just use a list item:
subnets: ["10.0.0.1/24","10.0.0.2/24"]
But that doesn't allow me to name or place the subnets where I want.
I know that I can also use multiple lists in Terraform, something like:
subnet_names: ["subnet1", "subnet2"]
subnets: ["10.0.0.1/24","10.0.0.2/24"]
subnet_az: ["us-west-1a", "us-west-1b"]
But that strikes me as messy and counter-intuitive. The last option I see is to mash everything togehter into an ugly list of strings, and then split them apart in Terraform:
things: ["subnet1__10.0.0.1/24__us-west-1a","subnet2__10.0.0.2/24__us-west-2a"]
But thats just ugly.
How can I deal with array/object-type of repeats in Terraform? For now I've just explicitly defined all my things, which caused a simple vpc definition to be 300 lines long :-(
As you've seen, at present Terraform doesn't support lists of structured data like you're trying to create here.
Having multiple flat lists of strings as you showed in your question is one common solution to this problem. It works, but as you've seen it's somewhat counter-intuitive to keep track of which values belong together that way.
An alternative approach that is likely to produce a more readable and maintainable result is to factor your aws_subnet resource out into a module that takes care of the elements that are always the same for all subnets. Then you can instantiate the module once per subnet, providing only the values that vary:
module "subnet1" {
source = "./subnet"
name = "subnet1"
cidr = "10.0.0.1/24"
az = "us-west-1a"
}
module "subnet2" {
source = "./subnet"
name = "subnet2"
cidr = "10.0.0.2/24"
az = "us-west-1b"
}
In many cases there's some sort of systematic relationship between AZs and CIDR blocks. If that's true for you then you can also use your module to encode these numbering rules. For example, in your subnet module:
variable "region_network_numbers" {
default = {
"us-west-1" = 0
"us-east-1" = 1
"us-west-2" = 2
}
}
variable "az_network_numbers" {
default = {
a = 1
b = 2
}
}
variable "base_cidr_block" {
default = "10.0.0.0/8"
}
variable "az" {
}
data "aws_availability_zone" "selected" {
name = "${var.az}"
}
resource "aws_subnet" "main" {
cidr_block = "${cidrsubnet(cidrsubnet(var.base_cidr_block, 8, var.region_network_numbers[data.aws_availability_zone.selected.region]), 4, var.az_network_numbers[data.aws_availability_zone.selected.name_suffix])}"
# ...
}
With this it's sufficient to provide just the az argument to the module, with the cidr and name produced systematically from the AZ name. This is the same general idea as shown in the example for the aws_availability_zone data source, and there's a more complete, elaborate example of this in the Terraform repository itself.

Resources