How to output ID from one resource which contains map variable - azure

I'm new in Terraform and I have silly question.I want to create NAT gateway and I need ID of one subnet:
resource "azurerm_subnet" "this" {
for_each = var.subnet_prefixes
name = each.key
resource_group_name = var.subnetRGname
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = each.value
}
variable subnet_prefixes {
type = map
default = {
"PublicSubnet"=["10.0.1.0/24"]
"PrivateSubnet"=["10.0.2.0/24"]
}
}
output "publicsubnet" {
value = map ("private", azurerm_subnet.this.id)
/* {
for s in azurerm_subnet.this : s.name => private.id
} */
}
In first block I'm creating subnets from "subnet_prefixes" map variables.
But then I want to output only "PublicSubnet" ID so I can join it to my NAT gateway.
Is there any simple way to do this?
Thank you very much for your help.
EDIT:
Well I tried to do something about it and it kinda works while using count, but it is not what I expected:
resource "azurerm_subnet" "this" {
count = length(var.subnet_prefixes)
name = var.subnetnames[count.index]
resource_group_name = var.subnetRGname
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = [var.subnet_prefixes[count.index]]
}
variable subnet_prefixes {
type = list(string)
default = ["10.0.1.0/24","10.0.2.0/24"]
}
variable subnetnames {
type = list(string)
default = ["SubnetA","SubnetB"]
}
output "publicsubnet" {
value = azurerm_subnet.this.*.id
}
module "NAT" {
source = "./Modules/NAT"
NATname = "${var.RGName}-NAT"
NATRGlocation = module.Resource_Group.resource_group_location
NATRGname = module.Resource_Group.resource_group_name
publicIPid = module.VNET.publicIP
publicsubnetID = module.VNET.publicsubnet[0]
}
I'm just wondering, as you can see on main module i need to specify index of subnet, and I wanted to export only ID that somehow refers to name like "Public*". Is there any way to do it, or maybe could you please tell me how to improve this code?

Since you've used for_each in your azurerm_subnet based on map of var.subnet_prefixes, you can refer to individuals subnets using keys from the map.
So public subnet id should be be:
output "publicsubnet" {
value = azurerm_subnet.this["PublicSubnet"].id
}

Related

Can't access attributes on a primitive-typed value (string)

I try to whitelist my apim public ips over my azure function :
apim.tf
data "azurerm_api_management" "main" {
name = "my-apim"
resource_group_name = "my-rg"
}
output "apim_ip" {
value = data.azurerm_api_management.main.public_ip_addresses
}
terraform output
apim_ip = tolist([
"1.2.3.4",
])
func.tf
resource "azurerm_linux_function_app" "az_func" {
name = var.my_func_name
resource_group_name = azurerm_resource_group.main.name
location = var.location
storage_account_name = azurerm_storage_account.main.name
storage_account_access_key = azurerm_storage_account.main.primary_access_key
service_plan_id = azurerm_service_plan.azfunc.id
site_config {
dynamic "ip_restriction" {
for_each = data.azurerm_api_management.main.public_ip_addresses
content {
ip_address = data.azurerm_api_management.main.public_ip_address_id.value
}
}
}
}
On terraform apply I keep having the error message :
Can't access attributes on a primitive-typed value (string).
What am I doing wrong?
data.azurerm_api_management.main.public_ip_address_id is a string, and therefore you cannot cannot access values from it as if it were a map or object type. I believe you meant to access values on the temporary lambda iterator variable assigned from data.azurerm_api_management.main.public_ip_addresses. In that, the usage and syntax would be:
ip_address = site_config.value.id
to access the id value from the current data.azurerm_api_management.main.public_ip_addresses attribute.

Terraform to create azure subnet delegation for selected subnets using for_each

I want some help on creating delegation on selected azure subnets. My code is as per below details.
Variables defined in my variable file
variable “subnets” {
type = map(any)
}
My tfvar file contains below values
subnets = {
mlops-aue-snt-aks = [“10.255.232.0/24”]
mlops-aue-snt-stg = [“10.255.233.0/26”]
mlops-aue-snt-kv = [“10.255.233.128/27”]
AzureBastionSubnet = [“10.255.233.160/27”]
mlops-aue-snt-shd = [“10.255.234.0/25”]
mlops-aue-snt-db1 = [“10.255.235.0/26”]
mlops-aue-snt-db2 = [“10.255.235.64/26”]
mlops-aue-snt-aci = [“10.255.235.128/26”]
}
This is my code for subnet
resource “azurerm_subnet” “azr_subnet” {
for_each = var.subnets
name = each.key
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value
enforce_private_link_endpoint_network_policies = true
}
All subnets are created with this and it is all fine but now I have a requirement to add service_delegation for mlops-aue-snt-db1 and mlops-aue-snt-db2 and different for other subnets. I am not sure how to achieve this with my existing code. I can’t separate out subnets from the code as it will force to delete existing ones and create new which is not recommended. I did read some posts about using dynamic block to make changes but not sure how to implement it for selective subnets as per my requirement.
Can anyone please suggest how to achieve this in terraform?
You can do this with dynamic blocks and by changing your subnets a bit.
subnets = {
mlops-aue-snt-aks = {
cidr = [“10.255.232.0/24”]
service_delegation = false
}
# the rest in same format
mlops-aue-snt-db1 = {
cidr = [“10.255.235.0/26”]
service_delegation = true
}
mlops-aue-snt-db2 = {
cidr = [“10.255.235.64/26”]
service_delegation = true
}
# ...
}
then
resource "azurerm_subnet" "azr_subnet" {
for_each = var.subnets
name = each.key
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value.cidr
enforce_private_link_endpoint_network_policies = true
dynamic "delegation" {
for_each = each.value.service_delegation == "true" ? [1] : []
content {
name = "delegation"
service_delegation {
name = "Microsoft.ContainerInstance/containerGroups"
actions = ["Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action"]
}
}
}
}
Check out this repo. This is not my work and I am not taking credit for it. I have just decided to share it so that it can be useful for devs looking for a solution for this common problem.

Terraform: How to set variables in a module based on a conditional?

I would like to pass a variable that will allow me to specify the list of VPC and subnet settings for an AWS instance. There are fixed VPC and subnet settings that make sense so I just want to allow a user to pick one using a single variable, i.e. use A or B.
For instance, let's say I have two available VPCs, and these are specified in a variables.tf file for a module my_instance:
variable "a_vpc_cidr_block" { default = "105.191.44.0/22" }
variable "a_vpc_id" { default = "id_a"}
variable "a_vpc_name" { default = "vpc_a" }
variable "a_subnet_availability_zone" { default = "us-east-1a" }
variable "a_subnet_cidr_block" { default = "105.191.25.0/25" }
variable "a_subnet_name" { default = "instance_A" }
variable "b_vpc_cidr_block" { default = "105.191.45.0/22" }
variable "b_vpc_id" { default = "id_b"}
variable "b_vpc_name" { default = "vpc_b" }
variable "b_subnet_availability_zone" { default = "us-east-1a" }
variable "b_subnet_cidr_block" { default = "105.191.35.0/25" }
variable "b_subnet_name" { default = "instance_B" }
The my_instance module will take a single input variable that an environment will specify, with a value of either 'A' or 'B' (is there a way to limit options for a variable to a list of values such as options=['A', 'B']?), and will be called like so in the terraform.tf for a Terraform configuration with a single instance:
module "my_instance" {
source = "../../modules/my_instance"
option = "A"
}
I want to now implement some logic within the module's main file (modules/my_instance/my_instance.tf) where it decides on which of the two collections of VPC and subnet settings it should use from the ones in modules/my_instance/variables.tf. I want to something like this (pseudocode):
if var.option == 'A'
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
else if var.option == 'B'
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
else
raise an error
# get a data resource identified by the VPC variables
data "aws_vpc" "instance_vpc" {
cidr_block = var.vpc_cidr_block
tags = {
Name = var.vpc_name
}
}
# get a data resource identified by the VPC variables
data "aws_subnet" "instance_subnet" {
vpc_id = var.vpc_id
cidr_block = var.subnet_cidr_block
availability_zone = var.subnet_availability_zone
tags = {
Name = var.subnet_name
}
}
# create an AWS key pair resource
resource "aws_key_pair" "instance_aws_key_pair" {
key_name = "component_key_${terraform.workspace}"
public_key = file("~/.ssh/terraform.pub")
}
# create the AWS EC2 instance
resource "aws_instance" "my_aws_instance" {
key_name = aws_key_pair.instance_aws_key_pair.key_name
ami = "ami-b12345"
instance_type = "t2.micro"
subnet_id = data.aws_subnet.instance_subnet.id
connection {
type = "ssh"
user = "terraform"
private_key = file("~/.ssh/terraform")
host = self.public_ip
}
tags = {
"Name" : "my_instance_name"
"Terraform" : "true"
}
}
Is this a matter of somehow using a count, something like this:
count = var.option == 'A'? 1 : 0
Is there a way to do this, or is there a better approach? I am very new to Terraform so I may be missing something obvious.
You have a couple of questions here.
Firstly, you should be able to use the newer, experimental custom validation rules to assert that a value is in a specific list of values.
Secondly, for determining which set of variables to use, I'd recommend going with a good old map in a local value.
For example,
locals {
vpc_info = {
"A" = {
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
}
"B" = {
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
}
}
}
Then you should be able to reference a specific field, within the chose option like the following
local.vpc_info[var.option].vpc_name
Let me know if this hits all your questions.

Terraform-12 (AWS): Create Subnets according to provided input variables using for/for_each

I want to create a module that can accept inputs for environments(dev,test,prod) and create the number of subnets ("app" and "db" subnet for each environment) with proper tags. e.g Name=dev-app
The module should be flexible to add/delete subnets as input variables are updated.
My template looks like below
variable "environments" {
type = map(object({
app_subnet_cidr = string
db_subnet_cidr = string
}))
default = {
dev = {
app_subnet_cidr = "192.168.219.0/24"
db_subnet_cidr = "192.168.218.0/24"
}
test = {
app_subnet_cidr = "192.168.118.0/24"
db_subnet_cidr = "192.168.119.0/24"
}
}
}
resource "aws_subnet" "this" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = {Don't know what to use here}
tags {
Name = {Don't know what to use here}
}
}
I was referring to the below articles.
https://www.hashicorp.com/blog/terraform-0-12-rich-value-types/
Question-2: How "networks" variable could be defined for below module
module "subnets" {
source = "./subnets"
parent_vpc_id = "vpc-abcd1234"
networks = {
production_a = {
network_number = 1
availability_zone = "us-east-1a"
}
production_b = {
network_number = 2
availability_zone = "us-east-1b"
}
staging_a = {
network_number = 1
availability_zone = "us-east-1a"
}
}
}
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/
The blog posts you are referring to were previews of forthcoming features not yet finished, so they are not the best reference for the final functionality that shipped. Instead, refer to the official documentation for resource for_each.
The documentation includes an example of declaring a number of azurerm_resource_group resource instances based on a map. We can adapt that example to work with your var.environments, with one resource block describing the "app" subnets, and a separate resource block describing the "db" subnets:
resource "aws_subnet" "app" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = each.value.app_subnet_cidr
tags = {
Name = "${each.key}-app"
}
}
resource "aws_subnet" "db" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = each.value.db_subnet_cidr
tags = {
Name = "${each.key}-db"
}
}
I'm not sure exactly how your second question relates to the first, but here's how to declare a variable networks so that it would accept a value like the one you showed in the second example.
variable "networks" {
type = map(object({
network_number = number
availability_zone = string
}))
}
If you want to see more about that second example, I'd suggest starting a new separate question on Stack Overflow about it. The convention on Stack Overflow is to have a separate question for each topic, rather than to ask multiple questions at once.

Terraform public ip output on Azure

I am following the example [1] to output the public IP of a new VM created on Azure with Terraform. It works fine when creating only 1 VM, but when I add a counter (default 2), it doesn't output anything.
This is how I am modifying the .tf file:
variable "count" {
default = "2"
}
...
resource "azurerm_public_ip" "test" {
name = "test-pip"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
public_ip_address_allocation = "Dynamic"
idle_timeout_in_minutes = 30
tags {
environment = "test"
}
}
...
data "azurerm_public_ip" "test" {
count = "${var.count}"
name = "${element(azurerm_public_ip.test.*.name, count.index)}"
resource_group_name = "${azurerm_virtual_machine.test.resource_group_name}"
}
output "public_ip_address" {
value = "${data.azurerm_public_ip.test.*.ip_address}"
}
After terraform apply:
Outputs:
public_ip_address = [
,
]
[1] https://www.terraform.io/docs/providers/azurerm/d/public_ip.html
The reason that you cannot output the multi public IPs is that you do not create multi public IPs. So when you use ${data.azurerm_public_ip.test.*.ip_address} to output them, there no these resources for you.
For the terraform, you could add the count in the resource azurerm_public_ip to create multi public IPs and output them with azurerm_public_ip.test.*.ip_address like this:
variable "count" {
default = "2"
}
...
resource "azurerm_public_ip" "test" {
count = "${var.count}"
name = "test-${count.index}-pip"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
public_ip_address_allocation = "Static"
idle_timeout_in_minutes = 30
tags {
environment = "test-${count.index}"
}
}
...
output "public_ip_address" {
value = "${azurerm_public_ip.test.*.ip_address}"
}
The screenshot of the result like this:
I did the test just create the public. So I change the allocation method into Static and output it with the resource.
If you want to use the data to reference the public IPs. The code would like this:
data "azurerm_public_ip" "test" {
count = "${var.count}"
name = "${element(azurerm_public_ip.test.*.name, count.index)}"
resource_group_name = "${azurerm_resource_group.test.name}"
}
output "public_ip_address" {
value = "${data.azurerm_public_ip.test.*.ip_address}"
}
Hope this will help you. If you need more help please let me know.
So I just ran into the exact same problem with my deployment into Azure.
The above solutions worked( Charles Xu's solution), with one caveat... I had to:
hard-code the name of the resource group,
as well as add a depends on clause to the output block.
I am sure that the above answers are the correct way to go about it however the value of the resource group key in the data object "azurerm_public_ip" needed to be hardcoded...

Resources