I'm trying to extract IP addresses from a range with Terraform.
For example, I defined this range 192.168.1.10-192.168.1.20 as a string and I would like to get a list like this: [192.168.1.10,192.168.1.11,…,192.168.1.20].
I checked for Terraform functions but didn’t find a way to do that.
Is this possible?
For further context, I am deploying MetalLB in a Kubernetes cluster and need to define the VIP range as a string like this 192.168.1.10-192.168.1.20.
The Kubernetes cluster is deployed on OpenStack and I need to configure Neutron OpenStack port to accept all IP addresses from this range:
resource "openstack_networking_port_v2" "k8s_worker_mgmt_port" {
name = "k8s_worker_mgmt_port"
network_id = data.openstack_networking_network_v2.k8s_openstack_mgmt_network_name.id
admin_state_up = "true"
allowed_address_pairs {
ip_address = "192.168.1.10"
}
allowed_address_pairs {
ip_address = "192.168.1.11"
}
}
....
}
If you can rely on the first 3 octets of the IP range being the same then you can get away with using a combination of split, slice, join, range and formatlist functions to do this natively inside Terraform with something like the following:
variable "ip_range" {
default = "192.168.1.10-192.168.1.20"
}
locals {
ip_range_start = split("-", var.ip_range)[0]
ip_range_end = split("-", var.ip_range)[1]
# Note that this naively only works for IP ranges using the same first three octects
ip_range_first_three_octets = join(".", slice(split(".", local.ip_range_start), 0, 3))
ip_range_start_fourth_octet = split(".", local.ip_range_start)[3]
ip_range_end_fourth_octet = split(".", local.ip_range_end)[3]
list_of_final_octet = range(local.ip_range_start_fourth_octet, local.ip_range_end_fourth_octet)
list_of_ips_in_range = formatlist("${local.ip_range_first_three_octets}.%s", local.list_of_final_octet)
}
output "list_of_ips_in_range" {
value = local.list_of_ips_in_range
}
This outputs the following:
list_of_ips_in_range = [
"192.168.1.10",
"192.168.1.11",
"192.168.1.12",
"192.168.1.13",
"192.168.1.14",
"192.168.1.15",
"192.168.1.16",
"192.168.1.17",
"192.168.1.18",
"192.168.1.19",
]
If you need to offset that range so you end up with IP addresses from .11 to .20 from the same input then you can do that by changing the local.list_of_final_octet like so:
list_of_final_octet = range(local.ip_range_start_fourth_octet + 1, local.ip_range_end_fourth_octet + 1)
Unfortunately Terraform doesn't have any built in functions for doing more elaborate CIDR math beyond cidrhost, cidrnetmask, cidrsubnet, cidrsubnets functions so if you have more complex requirements then you may need to delegate this to an external script that can calculate it and be called via the external data source.
Related
I am working with vm deployments over AWS with terraform(v1.0.9) as infrastructure as code. i have Terraform output.tf to print two lan a ips and code prints, list of lists like [["ip_a",],["ip_b",]] but i want a list like this ["ip_a", "ip_b"].
output.tf code
`output "foo" {
value = {
name = "xyz"
all_ips = tolist(aws_network_interface.vm_a_eni_lan_a.*.private_ips)
}
}`
printing -->
"name" = "xyz" "lan_a_ips" = tolist(\[ toset(\[ "10.0.27.116",\]), toset(\[ "10.0.28.201",\]), \])
but i want "lan_a_ips" = ["10.0.27.116", "10.0.28.201"]
I beleive tweaking output.tf can help. Any help is appreciated.
In your case, you have just set the splat expression [1] in a wrong place, i.e., instead of setting aws_network_interface.vm_a_eni_lan_a.private_ips[*] you set it to aws_network_interface.vm_a_eni_lan_a.*.private_ips. So you only need to change the output value:
output "foo" {
value = {
name = "xyz"
all_ips = aws_network_interface.vm_a_eni_lan_a.private_ips[*]
}
}
EDIT: The above applies when only a single instance of an aws_network_interface resource is created. For situations where there are multiple instance of this resource created with count meta-argument, the following can be used to get a list of IPs:
output "foo" {
value = {
name = "xyz"
all_ips = flatten([for i in aws_network_interface.test[*] : i.private_ips[*]])
}
}
Here, the for [2] loop is used to iterate over all the instances of a resource, hence the splat expression when referencing them aws_network_interface.test[*]. Additionally, since this will create a list of lists (as private_ips[*] returns a list), flatten [3] built-in function can be used to create a single list of IP addresses.
[1] https://www.terraform.io/language/expressions/splat
[2] https://www.terraform.io/language/expressions/for
[3] https://www.terraform.io/language/functions/flatten
here i'm trying to provision a aws classic ELB in a VPC where i have 2 public subnets. These subnets are also provisioned by terraform and i'm trying to pass both the subnets ids to elb module.SO the problem is i'm not able to give list input to elb subnets field
public_subnet variable works fine as i have used it for route table association it's just that i'm not able to handle the list and give it as input to vpc.
it works if i use subnets = [var.public_subnet.0,var.public_subnet.1]
here's my code
resource "aws_elb" "webelb" {
name = "foobar-terraform-elb"
#availability_zones = [var.public_subnet]
subnets = [var.public_subnet]
#
#
#
}
variable "public_subnet" {
type = list
}
subnet.tf
output "public_subnet" {
value = aws_subnet.public.*.id
}```
Error:
```Error: Incorrect attribute value type
on elb/elb.tf line 4, in resource "aws_elb" "webelb":
4: availability_zones = [var.public_subnet]
Inappropriate value for attribute "availability_zones": element 0: string
required.```
Since var.public_subnet is already a list. [var.public_subnet] is equivalent to [["192.168.0.0/32"]] instead of the expected, un-nested input ["102.168.0.0/32"]
ie...just use var.public_subnet
So, in my old .11 code, I have a file where i my output modules locals section, I'm building:
this_assigned_nat_ip = google_compute_instance.this_public.*.network_interface.0.access_config.0.assigned_nat_ip--
Which later gets fed to the output statement. This module could create N instances. So what it used to do was give me the first nat ip on the first access_config block on the first network interface of all the instances we created. (Someone locally wrote the code so we know that there's only going to be one network interface with one access config block).
How do I translate that to t12? I'm unsure of the syntax to keep the nesting.
Update:
Here's a chunk of the raw data out of a terraform show from tf11 (slightly sanitized)
module.gcp_bob_servers_ams.google_compute_instance.this_public.0:
machine_type = n1-standard-2
min_cpu_platform =
network_interface.# = 1
network_interface.0.access_config.# = 1
network_interface.0.access_config.0.assigned_nat_ip =
network_interface.0.access_config.0.nat_ip = 1.2.3.4
network_interface.0.access_config.0.network_tier = PREMIUM
Terraform show of equivalent host in tf12:
# module.bob.module.bob_gcp_ams.module.atom_d.google_compute_instance.this[1]:
resource "google_compute_instance" "this" {
allow_stopping_for_update = true
network_interface {
name = "nic0"
network = "https://www.googleapis.com/compute/v1/projects/stuff-scratch/global/networks/scratch-public"
network_ip = "10.112.112.6"
subnetwork = "https://www.googleapis.com/compute/v1/projects/stuff-scratch/regions/europe-west4/subnetworks/scratch-europe-west4-x-public-subnet"
subnetwork_project = "stuff-scratch"
access_config {
nat_ip = "35.204.132.177"
network_tier = "PREMIUM"
}
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
preemptible = false
}
}
If I understand correctly this_assigned_nat_ip is a list of IPs. You should be able to get the same thing in Terraform 0.12 by doing:
this_assigned_nat_ip = [for i in google_compute_instance.this_public : i.network_interface.0.access_config.0.assigned_nat_ip]
I did not test is, so I might have some small syntax error, but the for is the key to get that done.
Turns out this[*].network_interface[*].access_config[*].nat_ip[*] gave me what I needed. Given there's only every going to be one address on the interface, it comes out fine.
I have written a Terraform script to create a few Azure Virtual Machines.
The number of VMs created is based upon a variable called type in my .tfvars file:
type = [ "Master-1", "Master-2", "Master-3", "Slave-1", "Slave-2", "Slave-3" ]
My variables.tf file contains the following local:
count_of_types = "${length(var.type)}"
And my resources.tf file contains the code required to actual create the relevant number of VMs from this information:
resource "azurerm_virtual_machine" "vm" {
count = "${local.count_of_types}"
name = "${replace(local.prefix_specific,"##TYPE##",var.type[count.index])}-VM"
location = "${azurerm_resource_group.main.location}"
resource_group_name = "${azurerm_resource_group.main.name}"
network_interface_ids = ["${azurerm_network_interface.main.*.id[count.index]}"]
vm_size = "Standard_B2ms"
tags = "${local.tags}"
Finally, in my output.tf file, I output the IP address of each server:
output "public_ip_address" {
value = ["${azurerm_public_ip.main.*.ip_address}"]
}
I am creating a Kubernetes cluster with 1x Master and 1x Slave VM. For this purpose, the script works fine - the first IP output is the Master and the second IP output is the Slave.
However, when I move to 8+ VMs in total, I'd like to know which IP refers to which VM.
Is there a way of amending my output to include the type local, or just the server's hostname alongside the Public IP?
E.g. 54.10.31.100 // Master-1.
Take a look at formatlist (which is one of the functions for string manipulations) and can be used to iterate over the instance attributes and list tags and other attributes of interest.
output "ip-address-hostname" {
value = "${
formatlist(
"%s:%s",
azurerm_public_ip.resource_name.*.fqdn,
azurerm_public_ip.resource_name.*.ip_address
)
}"
}
Note this is just a draft pseudo code. You may have to tweak this and create additional data sources in your TF file for effective enums
More reading available - https://www.terraform.io/docs/configuration/functions/formatlist.html
Raunak Jhawar's answer pointed me in the right direction, and therefore got the green tick.
For reference, here's the exact code I used in the end:
output "public_ip_address" {
value = "${formatlist("%s: %s", azurerm_virtual_machine.vm.*.name, azurerm_public_ip.main.*.ip_address)}"
}
This resulted in the following output:
I need to create a string parameter to pass to the aws-cli via local-exec so need to combine two lists from the remote state into the required format, cannot think of a good way to do this using the inbuilt interpolation functions.
Required string format
"SubnetId=subnet-x,Ip=ip_x SubnetId=subnet--y,Ip=ip_y SubnetId=subnet-z,Ip=ip_z"
We have the subnets and corresponding cidrs in two separate lists.
["subnet-x","subnet-y","subnet-z"]
["cidr-x","cidr-y","cidr-z"]
Was thinking I could use the cidrhost function to obtain the IPs but cannot see a way to format the two lists into the one string.
Try using formatlist followed by join.
locals {
# this should give you
formatted_list = "${formatlist("SubnetId=%s,Ip=%s", var.subnet_list, var.cidrs_list}"
# combine the formatted list of parameter together using join
cli_parameter = "${join(" ", locals.formatted_list)}"
}
EDIT: You will need to use a null_resource to convert the CIDRs to IP Addresses like in the other answer. Then you can just build the formatted_list and cli_parameter similar to before.
locals {
subnet_list = ["subnet-x","subnet-y","subnet-z"]
cidr_list = ["cidr-x","cidr-y","cidr-z"]
# this should give you
formatted_list = "${formatlist("SubnetId=%s,Ip=%s", var.subnet_list, null_resource.cidr_host_convert.*.triggers.value)}"
# combine the formatted list of parameter together using join
cli_parameter = "${join(" ", locals.formatted_list)}"
}
resource "null_resource" "cidr_host_convert" {
count = "${length(locals.cidr_list}"
trigger = {
# for each CIDR, get the first IP Address in it. You may need to manage
# the index value to prevent overlap
desired_ips = "${cidrhost(locals.cidr_list[count.index], 1)}"
}
}
One of the guys at work came up with this,
variable "subnet_ids" {
default = ["subnet-345325", "subnet-345243", "subnet-345234"]
}
variable "cidrs" {
default = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/23"]
}
resource "null_resource" "subnet_strings_option_one" {
count = "${length(var.subnet_ids)}"
triggers {
value = "SubnetId=${var.subnet_ids[count.index]},Ip=${cidrhost(var.cidrs[count.index],11)}"
}
}
output "subnet_strings_option_one" {
value = "${join("",null_resource.subnet_strings_option_one.*.triggers.value)}"
}
This gives the following output
null_resource.subnet_strings_option_one[1]: Creating...
triggers.%: "" => "1"
triggers.value: "" => "SubnetId=subnet-345243,Ip=10.0.1.11"
null_resource.subnet_strings_option_one[2]: Creating...
triggers.%: "" => "1"
triggers.value: "" => "SubnetId=subnet-345234,Ip=10.0.2.11"
null_resource.subnet_strings_option_one[0]: Creating...
triggers.%: "" => "1"
triggers.value: "" => "SubnetId=subnet-345325,Ip=10.0.0.11"
null_resource.subnet_strings_option_one[2]: Creation complete after 0s (ID: 852839482792384695)
null_resource.subnet_strings_option_one[1]: Creation complete after 0s (ID: 5439264637705543321)
null_resource.subnet_strings_option_one[0]: Creation complete after 0s (ID: 1054498808481879719)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
subnet_strings_option_one = SubnetId=subnet-345325,Ip=10.0.0.11 SubnetId=subnet-345243,Ip=10.0.1.11 SubnetId=subnet-345234,Ip=10.0.2.11