How do I assign random subnet_id into azurerm_network_interface? - azure

### variable
variable "vm-subnets" {
type = list(string)
default = ["7.0.1.0/24","7.0.2.0/24","7.0.3.0/24"]
}
### subnet
resource "azurerm_subnet" "task_subnet" {
name = "subnet-${format("%02d",count.index)}"
resource_group_name = azurerm_resource_group.task.name
virtual_network_name = azurerm_virtual_network.task_vnet.name
network_security_group_id = azurerm_network_security_group.task_nsg.id
address_prefix = var.vm-subnets[count.index]
count = length(var.vm-subnets)
}
### NIC
resource "azurerm_network_interface" "vm_nic" {
name = "nic--${format("%02d",count.index)}"
location = var.region
resource_group_name = azurerm_resource_group.task.name
count = var.vm-count
ip_configuration {
name = "${var.resource_prefix}-${format("%02d",count.index)}-ip"
subnet_id = azurerm_subnet.task_subnet.*.id[count.index]
private_ip_address_allocation = "dynamic"
public_ip_address_id = azurerm_public_ip.task_public_ip.*.id[count.index]
}<br>
}
I need to 7 VM into 3 subnet for like subnet-A = 2VMs ,subnet-B=2VMs, subnet-C = 3VMs or randomly
``
ERROR:
Error: Invalid index
on vm-network.tf line 11, in resource "azurerm_network_interface" "vm_nic":
11: subnet_id = azurerm_subnet.task_subnet.*.id[count.index]
|----------------
| azurerm_subnet.task_subnet is tuple with 3 elements
| count.index is 4
The given key does not identify an element in this collection value.
Error: Invalid index
on vm-network.tf line 11, in resource "azurerm_network_interface" "vm_nic":
11: subnet_id = azurerm_subnet.task_subnet.*.id[count.index]
|----------------
| azurerm_subnet.task_subnet is tuple with 3 elements
| count.index is 3
The given key does not identify an element in this collection value.
What modification can be done to resolve it and how can I assign different/random subnet on each vm rather then count loop.
I also try to do it using random_shuffle and set-product function but not get the desired output .. please Help

After 2 days of logical struggle i finally able to find a solution or the questions that I create : USING element function https://www.terraform.io/docs/configuration/functions/element.html
NIC
resource "azurerm_network_interface" "vm_nic" {
name = "nic--${format("%02d",count.index)}"
location = var.region
resource_group_name = azurerm_resource_group.task.name
count = var.vm-count
ip_configuration {
name = "${var.resource_prefix}-${format("%02d",count.index)}-ip"
subnet_id = element(azurerm_subnet.task_subnet.*.id, count.index)
private_ip_address_allocation = "dynamic"
public_ip_address_id = azurerm_public_ip.task_public_ip.*.id[count.index]
}
}
subnet_id = element(azurerm_subnet.task_subnet.*.id, count.index)
using element function ["7.0.1.0/24","7.0.2.0/24","7.0.3.0/24"] at vm count.index = 3 it goes back to subnet index = 0 so that how it works and tested it at vm.count = 5, 7 , 10. ✌🏻

Related

Terraform : How to loop over aws_instance N times as defined within object

I have the following variable
variable "instance_types" {
default = {
instances : [
{
count = 1
name = "control-plane"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
{
count = 3
name = "worker"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
]
}
}
With the following instance declaration (that I'm attempting to iterate)
resource "aws_instance" "k8s-node" {
# Problem here : How to turn an array of 2 objects into 4 (1 control_plane, 3 workers)
for_each = {for x in var.instance_types.instances: x.count => x}
ami = lookup(each.value, "ami")
instance_type = lookup(each.value, "instance_type")
iam_instance_profile = lookup(each.value, "iam_instance_profile")
subnet_id = lookup(each.value, "subnet_id")
tags = {
Name = lookup(each.value, "name")
Type = each.key
}
}
Goal: Get the aws_instance to iterate 4 times (1 control_plane + 3 workers) and populate the values the index of instance_types.
Problem : Cannot iterate the over the object array correctly with desired result. In a typical programming language this would be achieved in a double for loop.
This can be solved easier with a data type of map(object)) for your input variable. The transformed data structure appears like:
variable "instance_types" {
...
default = {
"control-plane" = {
count = 1
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
"worker" = {
count = 3
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
}
}
Note the name key in the object is subsumed into the map key for efficiency and cleanliness.
If the resources are split between the control plane and worker nodes, then we are finished and can immediately leverage this variable's value in a for_each meta-argument. However, combining the resources now requires a data transformation:
locals {
instance_types = flatten([ # need this for final structure type
for instance_key, instance in var.instance_types : [ # iterate over variable input objects
for type_count in range(1, instance.count + 1) : { # sub-iterate over objects by "count" value specified; use range function and begin at 1 for human readability
new_key = "${instance_key} ${type_count}" # for resource uniqueness
type = instance_key # for easier tag value later
ami = instance.ami # this and below retained from variable inputs
instance_type = instance.instance_type
iam_instance_profile = instance.iam_instance_profile
subnet_id = instance.subnet_id
}
]
])
}
Now we can iterate within the resource with the for_each meta-argument, and utilize the for expression to reconstruct the input for suitable usage within the resource.
resource "aws_instance" "k8s-node" {
# local.instance_types is a list of objects, and we need a map of objects with unique resource keys
for_each = { for instance_type in local.instance_types : instance_type.new_key => instance_type }
ami = each.value.ami
instance_type = each.value.instance_type
iam_instance_profile = each.value.iam_instance_profile
subnet_id = each.value.subnet_id
tags = {
Name = each.key
Type = each.value.type
}
}
This will give you the behavior you desire, and you can modify it for style preferences or different uses as the need arises.
Note the lookup functions are removed since they are only useful when default values are specified as a third argument, and that is not possible in object types within variable declarations except as an experimental feature in 0.14.
The absolute namespace for these resources' exported resource attributes would be:
(module?.<declared_module_name>?.)<resource_type>.<resource_name>[<resource_key>].<attribute>
For example, given an intra-module resource, first worker node, and private ip address exported attribute:
aws_instance.k8s-node["worker 1"].private_ip
Note you can also access all resources' exported attributes by terminating the namespace at <resource_name> (retaining the map of all resources instead of accessing a singular resource value). Then you could also use a for expression in an output declaration to create a custom aggregate output for all of the similar resources and their identical exported attribute(s).
{ for node_key, node in aws_instance.k8s-node : node_key => node.private_ip }

I have created 4 subnets and created output values to get all subnets ids in one variable .So how can I plan to retrieve 2 values to attach the nics

Error: Unbalanced parentheses
on .terraform\modules\nics\main.tf line 19, in resource "azurerm_network_interface" "NIC1":
19: subnet_id = "${element(var.subnetwork-subnetid.*.id, (0,1))}"
output values of subnets:
output "subnetwork-subnetid" {
value = concat(azurerm_subnet.subnetwork.*.id, azurerm_subnet.subnetwork6.*.id)
}
nic.tf
resource "azurerm_network_interface" "NIC1" {
#count = "${length(var.subnetwork-subnetid)}"
#for_each= toset(var.subipv4)
count = "${length(var.subipv4)}"
name = "${lookup(element(var.subipv4, count.index), "name")}"
#name = var.nic-name
location = var.rg-location
resource_group_name = var.rg-name
enable_ip_forwarding = true
enable_accelerated_networking = true
ip_configuration {
name = "ipconfig"
subnet_id = "${element(var.subnetwork-subnetid.*.id, (0,1))}"
private_ip_address_allocation = "Dynamic"
#public_ip_address_id = azurerm_public_ip.pubip.id
#public_ip_address_id = azurerm_public_ip.pubip.*.id
primary = true
}
tags = {
name = "${lookup(element(var.subipv4, count.index), "name")}"
}
}```
Please someone help me in this issue.Thanks!
Second argument in element is index:
index finds the index for a particular element value.
Thus to get few elements from the list based on indices, you can do:
subnet_id = [ for idx in [0, 1]: element(var.subnetwork-subnetid.*.id, idx) ]
If you want a range of indies, you can use slice:
subnet_id = slice(var.subnetwork-subnetid.*.id, 0, 2)

How to evaluate for_each in terraform resource

I have below Terraform code which creates multiple resources using for_each. I wrote the code by referencing many for_each examples but still getting error. Can someone please verify the code and help.
variables.tf
variable "user_ds" {
default = ["a-b", "c-d", "d-e", "f-g"]
}
main.tf
resource "aws_sagemaker_notebook_instance_lifecycle_configuration" "life_cycle" {
for_each = toset(var.users)
name = "lifecycle-${var.users[each.key]}"
on_start = var.on_start
on_create = var.on_create
}
resource "aws_sagemaker_notebook_instance" "sagemaker" {
for_each = toset(var.users)
name = "${var.notebook_prefix}-${var.users[each.key]}-notebook"
role_arn = length(var.role_arn) > 1 ? element(var.role_arn, [each.key]) : ""
instance_type = var.instance_type
kms_key_id = var.kms_key_id
direct_internet_access = "Disabled"
subnet_id = var.subnet_id
security_groups = var.security_groups
lifecycle_config_name = "lifecycle-${var.users[each.key]}"
tags = {
Name = "${var.notebook_prefix}-${var.users[each.key]}-notebook"
aws-glue-dev-endpoint = var.glue_endpoint
}
}
When I run terraform apply, I am getting below error :-
on ../../../modules/sagemaker-new/main.tf line 8, in resource "aws_sagemaker_notebook_instance_lifecycle_configuration" "life_cycle":
8: name = "lifecycle-${var.users[each.key]}"
|----------------
| each.key is "a-b"
| var.users is list of string with 4 elements
The given key does not identify an element in this collection value: a number
is required.
on ../../../modules/sagemaker-new/main.tf line 15, in resource "aws_sagemaker_notebook_instance" "sagemaker":
15: name = "${var.notebook_prefix}-${var.users[each.key]}-notebook"
|----------------
| each.key is "a-b"
| var.users is list of string with 4 elements
The given key does not identify an element in this collection value: a number
is required.
on ../../../modules/sagemaker-new/main.tf line 16, in resource "aws_sagemaker_notebook_instance" "sagemaker":
16: role_arn = length(var.role_arn) > 1 ? element(var.role_arn, [each.key]) : ""
|----------------
| each.key is "a-b"
Invalid value for "index" parameter: number required.
on ../../../modules/sagemaker-new/main.tf line 22, in resource "aws_sagemaker_notebook_instance" "sagemaker":
22: lifecycle_config_name = "lifecycle-${var.users[each.key]}"
|----------------
| each.key is "a-b"
| var.users is list of string with 4 elements
The given key does not identify an element in this collection value: a number
is required.
25: Name = "${var.notebook_prefix}-${var.users[each.key]}-notebook"
|----------------
| each.key is "a-b"
| var.users is list of string with 4 elements
The given key does not identify an element in this collection value: a number
is required.
Any help to the above code will be appreciated.
Thanks in advance

Terraform - unable to define security groups with terraform-aws-modules/security-group/aws

I am trying examples from this module
https://registry.terraform.io/modules/terraform-aws-modules/security-group/aws/3.10.0
The main.tf:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "${var.environment}-project-vpc"
cidr = "10.0.0.0/16"
#
# Important!
# https://github.com/terraform-aws-modules/terraform-aws-vpc/issues/403
# Only append or delete from the end of the list
#
azs = ["us-east-2a", "us-east-2b", "us-east-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
one_nat_gateway_per_az = false
enable_dns_hostnames = true
enable_dns_support = true
tags = module.project_config.tags
}
module "bastion_sg" {
source = "terraform-aws-modules/security-group/aws"
name = "bastion-service"
description = "Security group for bastion-service"
vpc_id = module.vpc.default_vpc_id
ingress_rules = ["https-443-tcp", "http-80-tcp", "ssh", "all-icmp"]
egress_rules = ["all-all"]
}
resource "aws_instance" "bastion" {
# name = "bastion"
# description = "bastion ssh host to access internals of the infrastructure by SSH"
ami = "ami-08ee2516c7709ea48"
instance_type = "t2.micro"
security_groups = [
module.bastion_sg.this_security_group_id
]
subnet_id = module.vpc.public_subnets[0]
}
and terraform apply fails with
Error: Invalid index
on .terraform/modules/bastion_sg/terraform-aws-security-group-3.10.0/main.tf line 65, in resource "aws_security_group_rule" "ingress_rules":
65: description = var.rules[var.ingress_rules[count.index]][3]
|----------------
| count.index is 2
| var.ingress_rules is list of string with 4 elements
| var.rules is map of list of string with 115 elements
The given key does not identify an element in this collection value.
Error: Invalid index
on .terraform/modules/bastion_sg/terraform-aws-security-group-3.10.0/main.tf line 67, in resource "aws_security_group_rule" "ingress_rules":
67: from_port = var.rules[var.ingress_rules[count.index]][0]
|----------------
| count.index is 2
| var.ingress_rules is list of string with 4 elements
| var.rules is map of list of string with 115 elements
The given key does not identify an element in this collection value.
Error: Invalid index
on .terraform/modules/bastion_sg/terraform-aws-security-group-3.10.0/main.tf line 68, in resource "aws_security_group_rule" "ingress_rules":
68: to_port = var.rules[var.ingress_rules[count.index]][1]
|----------------
| count.index is 2
| var.ingress_rules is list of string with 4 elements
| var.rules is map of list of string with 115 elements
The given key does not identify an element in this collection value.
Error: Invalid index
on .terraform/modules/bastion_sg/terraform-aws-security-group-3.10.0/main.tf line 69, in resource "aws_security_group_rule" "ingress_rules":
69: protocol = var.rules[var.ingress_rules[count.index]][2]
|----------------
| count.index is 2
| var.ingress_rules is list of string with 4 elements
| var.rules is map of list of string with 115 elements
The given key does not identify an element in this collection value.
What am I doing wrong?
Ok, figured this out
module "bastion_sg" {
source = "terraform-aws-modules/security-group/aws"
name = "bastion-service"
description = "Security group for bastion-service"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["0.0.0.0/0", module.vpc.vpc_cidr_block]
ingress_rules = ["https-443-tcp", "http-80-tcp", "ssh-tcp", "all-icmp"]
egress_rules = ["all-all"]
}
Correct name of the rule is "ssh-tcp", not "ssh"

Terraform - reference instance value when calling module throwing errors

I have the block of code below that creates a bunch of subnets based on a list of names and a list of address_prefixes.
resource "azurerm_subnet" "subnet" {
count = "${length(var.subnet_names)}"
name = "${element(var.subnet_names, count.index)}"
resource_group_name = "${var.vnet_rg_name}"
virtual_network_name = "${data.azurerm_virtual_network.vnet.name}"
address_prefix = "${element(var.subnet_prefixes, count.index)}"
service_endpoints = ["Microsoft.Sql","Microsoft.Storage","Microsoft.AzureCosmosDB"]
network_security_group_id = "${data.azurerm_network_security_group.required_nsg.id}"
route_table_id = "${element(azurerm_route_table.routetable.*.id, count.index)}"
depends_on = ["azurerm_route_table.routetable"]
}
I am then trying to create some routes using a module but when I try to pass in values for variables using properties from a specific instance of the azurerm_subnet.subnet resource, it throws the error:
"module.insidedmzroutes.var.subnet_name: Resource 'azurerm_subnet.subnet' not found for variable 'azurerm_subnet.subnet.5.name'"
module "insidedmzroutes" {
source = "./modules/dmzroutes"
subnet_name = "${azurerm_subnet.subnet.5.name}"
vnet_rg = "${data.azurerm_resource_group.vnet_rg.name}"
route_table_name = "${azurerm_route_table.routetable.5.name}"
next_hop_ip = "${cidrhost(azurerm_subnet.subnet.5.address_prefix, 4)}"
subnet_names = ["${var.subnet_names}"]
subnet_prefixes = ["${var.subnet_prefixes}"]
}
Does this not work or do I have the reference constructed incorrectly?
Please have a look at the Terraform interpolation syntax documentation, look for interpolation syntax.
The following would work (as indicated by Adil B):
subnet_name = "${azurerm_subnet.subnet.*.name[5]}"
As with the splat syntax * you select all of the elements created using a count variable, which will then return a list, which you can select the correct element from [5].
However, why are you also passing along the entire list of subnets? Which subnets are these? It's not very clear from your code if these are the 5 subnets you created earlier or different ones. Are you creating an insidedmzroutes for every subnet? If so, I'd get rid of the subnet_name var and instead implement something like this in the resource inside the module:
count = "${length(var.subnet_names)}"
subnet_name = "${element(var.subnet_names, count.index)}"

Resources