Terraform use Variable from random function - terraform

I´m a beginner in Terraform.
I like to know, how can I use a variable which was generated in a random function in the same .tr script and use it like below for a different input. I´m also not sure, if the random stuff is working correctly? Can someone help me? But how to pass the varibale?
for example
locals {
dslist = [
"Datastore1",
"Datastore2",
"Datastore3"
]
}
resource "random_shuffle" "random_dslist" {
input = local.dslist
result_count = 1
}
output "random_dslist" {
value = random_shuffle.random_dslist.result
}
data "vsphere_datastore" "datastore" {
name = "${var.random_dslist}" # -> how can I pass the generated random datastore in here?
datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

You can use the value of the resource just like you already used it for the output:
Only difference here is that, according to the documentation random_shuffle.result gives you a list, since you can get more than one random result. Therefore, just take the first result [0], because you know that you expect only one item in the list.
resource "random_shuffle" "random_dslist" {
input = local.dslist
result_count = 1
}
data "vsphere_datastore" "datastore" {
name = random_shuffle.random_dslist.result[0]
datacenter_id = data.vsphere_datacenter.dc.id
}

Related

Terraform: referencing each.key or each.value in module when calling variables

I'm trying to achieve (maybe by wrong means) something like that. I'd like to be able to create few types of endpoints in Azure (KV, SA for example).
module "endpoints" {
source = "./modules/private_endpoint"
for_each = toset(var.endpoint_type)
private_connection_resource_id = "var.${each.value}.private_connection_resource_id"
Where:
Endpoint_type is a list of endpoints (its value is "storage_account"),
private_connection_resource_id is in map(any) which looks like (there are other values, but I don't think they're important at this point):
storage_account = {
private_connection_resource_id = #VALUE
...
private_connection_resource_id = "var.${each.value}.private_connection_resource_id" --- this gets translated to literal string (var.storage_account.private_connection_resource_id), where I'd like it to get translated to exact value - the id of storage account (it's hardcoded in tfvars).
Thank you in advance for any tips!
Edit: It appears that Im as dumb as they come. Should've changed the map a bit:
endpoint_type = {
storage_account = {
private_connection_resource_id = #VALUE
...
And ref in module calling to: each.value.private_connection_resource_id
You cannot construct an expression inside a string and then evaluate it. Terraform always parses the full configuration first and then executes it once already parsed.
If you want to look up values dynamically based on a key then a map is the most appropriate data structure to use for that purpose. For example, you could define a input variables endpoint_types and endpoints like this:
variable "endpoint_types" {
type = map(object({
private_connection_resource_id = string
}})
}
variable "endpoints" {
type = map(object({
type = string
}))
}
My intent with the above example is that the type attribute inside the endpoints objects is a lookup key for the other map in endpoint_types.
When you then define your module block with for_each you will first refer to var.endpoints and then look up an appropriate endpoint type in var.endpoint_types based on its selected key:
module "endpoints" {
source = "./modules/private_endpoint"
for_each = var.endpoints
private_connection_resource_id = var.endpoint_types[each.value.type].private_connection_resource_id
}
The user of the outer module would need to provide both a map of endpoints and a map describing all of the possible types those endpoints might have, like this:
endpoints = {
storage_1 = {
type = "storage"
}
storage_2 = {
type = "storage"
}
other_1 = {
type = "other"
}
}
endpoint_types = {
storage = {
private_connection_resource_id = "something"
}
other = {
private_connection_resource_id = "something_else"
}
}

Terraform try/catch in a module?

Terraform v0.12.x, AWS provider
I'm trying to write a generic module that will either return an existing EBS snapshot of a passed-in EBS volume name, or take a snapshot of a passed-in EBS volume name. Either way it should return a snapshot id.
Here's the code to get an existing snapshot.
data "aws_ebs_snapshot" "snapshot" {
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_id]
}
filter {
name = "status"
values = ["completed"]
}
}
output "snapshot_id" {
value = data.aws_ebs_snapshot.snapshot.id
description = "Jenkins master snapshot id"
}
and here's the code to take a snapshot.
data "aws_ebs_volume" "ebs" {
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_id]
}
}
// Take a snapshot of the green EBS resource
resource "aws_ebs_snapshot" "snapshot" {
volume_id = data.aws_ebs_volume.ebs.id
}
output "snapshot_id" {
value = aws_ebs_snapshot.snapshot.id
description = "Jenkins master snapshot id"
}
Is it possible to do this? If so how? I know I can separate them into 2 separate modules, but humor me.
# try/catch block is of course pseudo-code
try {
# Get an existing snapshot id
data "aws_ebs_snapshot" "snapshot" {
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_name]
}
filter {
name = "status"
values = ["completed"]
}
}
output "snapshot_id" {
value = data.aws_ebs_snapshot.snapshot.id
description = "Jenkins master snapshot id"
}
}
catch() {
# Get the volume id and take a snapshot
data "aws_ebs_volume" "ebs" {
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_id]
}
}
// Take a snapshot of the green EBS resource
resource "aws_ebs_snapshot" "snapshot" {
volume_id = data.aws_ebs_volume.ebs.id
}
output "snapshot_id" {
value = aws_ebs_snapshot.snapshot.id
description = "Jenkins master snapshot id"
}
}
I know try/catch blocks are not used this way in Terraform, so how can I achieve what I want?
The situation you've described doesn't seem like one where it's necessary to make a dynamic decision based on the remote system, because you can tell entirely from the input variables whether the caller is specifying a snapshot id or a volume id:
variable "ebs_name" {
type = string
default = null
}
variable "ebs_id" {
type = string
default = null
}
data "aws_ebs_snapshot" "snapshot" {
count = var.ebs_name != null ? 1 : 0
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_name]
}
filter {
name = "status"
values = ["completed"]
}
}
data "aws_ebs_volume" "ebs" {
count = var.ebs_id != null ? 1 : 0
most_recent = true
filter {
name = "tag:Name"
values = [var.ebs_id]
}
}
// Take a snapshot of the green EBS resource
resource "aws_ebs_snapshot" "snapshot" {
count = var.ebs_id != null ? 1 : 0
volume_id = data.aws_ebs_volume.ebs[count.index].id
}
output "snapshot_id" {
# Return either the generated snapshot or the given
# snapshot. If the caller specified both for some
# reason then the generated snapshot takes priority.
# This will produce an error if neither var.ebs_name
# nor var.ebs_id is set, because the result will have
# no elements.
value = concat(
aws_ebs_snapshot.snapshot[*].id,
data.aws_ebs_snapshot.snapshot[*].id,
)[0]
description = "Jenkins master snapshot id"
}
For completeness in case someone else finds this answer in future I want to note that the Module Composition guide suggests just directly writing out the straightforward read or create code for each case rather than making dynamic decisions in cases like these, but I showed the dynamic example above because you suggested (by reference to the possibility of using two modules to address this) that you'd already considered and decided against using a composition style.
At first glance you might think that you could check if the snapshot exists via the data source, and then use something like count on the resource to create one if the data source didn't return anything. Unfortunately that's not how Terraform works because the data source will throw an error if it can't find a match, causing Terraform to exit.
See the official response from HashiCorp here, when asked for the sort of capability you are looking for.
The sort of dynamic decision-making that is being requested here runs
counter to Terraform's design goals, since it makes the configuration
a description of what might possibly be rather than what is.
In general this sort of thing is handled better via AWS CLI scripts, or something like a Python/boto3 script, instead of Terraform.

Terraform - Variable inside a variable

I would like to use a variable inside a variable.
This is my resource:
resource "aws_route" "vpc_peering_accepter" {
provider = "aws.accepter"
count = length(data.terraform_remote_state.vpc.outputs.${var.region}-vpc-private_routing_tables)
route_table_id = tolist(data.terraform_remote_state.vpc.outputs.${var.region}-vpc-private_routing_tables)[count.index]
destination_cidr_block = var.vpc_cidr
vpc_peering_connection_id = aws_vpc_peering_connection.peer.*.id[0]
}
Of course this one is not working.
What's the best practice to do it?
Thanks,
Elad
You can combine Local Values with the lookup function to accomplish this.
In the following example the null datasource is mimicking data.terraform_remote_state.vpc.outputs:
variable "region" {
default = "us-east1"
}
locals {
vpc_private_routing_tables = "${var.region}-vpc-private_routing_tables"
}
data "null_data_source" "values" {
inputs = {
us-east1-vpc-private_routing_tables = "11111111"
us-east2-vpc-private_routing_tables = "22222222"
}
}
output "vpc_peering" {
value = lookup(data.null_data_source.values.inputs, local.vpc_private_routing_tables)
}
Because data.terraform_remote_state.vpc.outputs is a mapping, you can use either attribute syntax or index syntax to access the values inside:
Attribute syntax: data.terraform_remote_state.vpc.outputs.us-west-1-vpc-private_routing_tables
Index syntax: data.terraform_remote_state.vpc.outputs["us-west-1-vpc-private_routing_tables"]
An advantage of index syntax is that you can use any expression within those brackets as long as its result is a string. In particular, you can use the template interpolation syntax:
data.terraform_remote_state.vpc.outputs["${var.region}-vpc-private_routing_tables"]
With that said, in this sort of situation where you are producing the same information for a number of different objects -- regions, in this case -- it's more conventional to gather all of these values into a single mapping when you declare the output, so that these related values are explicitly grouped together in a single collection. For example:
output "vpc_private_routing_table_ids" {
value = {
us-east-1 = aws_route_table.us-east-1.id
us-west-2 = aws_route_table.us-west-2.id
}
}
Then from the perspective of the consumer -- that is, the module that is using data "terraform_remote_state" to access these outputs -- this appears as a simple map keyed by region:
data.terraform_remote_state.vpc.outputs.vpc_private_routing_table_ids[var.region]
If you are producing many different objects on a per-region basis then you might choose to gather all of their ids together into a single output, which might be more convenient to use elsewhere:
output "regions" {
value = {
us-east-1 = {
vpc_id = aws_vpc.us-east-1.id
subnet_ids = aws_subnet.us-east-1[*].id
private_route_table_id = aws_route_table.us-east-1.id
}
us-west-1 = {
vpc_id = aws_vpc.us-west-1.id
subnet_ids = aws_subnet.us-west-1[*].id
private_route_table_id = aws_route_table.us-west-1.id
}
}
}
...which would then appear as follows in the consumer module:
data.terraform_remote_state.vpc.outputs.regions[var.region].private_route_table_id
Ultimately you can structure your output values however you like, but I'd recommend choosing a shape that optimizes for clarity in the configuration that is referring to the data. That usually means making the referring expressions as simple as possible, and ideally avoiding complex expressions like string template syntax whenever possible.

Construct list of maps from a csv in terraform

I have the following variable
variable "whitelisted_ips" {
default = "xx.xxx.xx.x/21,xxx.xx.xxx.x/20"
}
I use this some places where a list of CIDRS is needed using the following
cidr_blocks = ["${split(",", var.whitelisted_ips)}"]
That all works fine.
I want to reuse these values and end up with the following structure (expressed as JSON to give you an idea)
waf_ips = [
{ value = "xx.xxx.xx.x/21", type="IPV4"},
{ value = "xxx.xx.xxx.x/20", type="IPV4"},
]
So I am looking to create a list of maps from the string (the IPV4 is hardcoded and repeats on every line).
If I feed my current JSON to an aws_waf_rule and treat it as a list it succeeds, but I'd rather not repeat the data in the tfvars file as its the same and I'd like to reuse that string separated list.
Ok so having learned more and read more it turns out you can do this with a null resource for static data so.
locals {
cidr_blocks = ["xxx.xxx.xxx/23", "xxx.xxx.xxx/23", "xxx.xxx.xxx/23"]
}
resource "null_resource" "cidr_map_to_protocol" {
count = "${length(local.cidr_blocks)}"
triggers = {
value = "${element(local.cidr_blocks, count.index)}"
type = "IPV4"
}
}
output "mapped_cidr_to_protocol" {
value = "${null_resource.cidr_map_to_protocol.*.triggers}"
}
this will not work for a computed resource unfortunately.

Terraform dynamic variable

I'm creating subnets as part of a seperate terraform template and exporting the IDs as follows.
output "subnet-aza-dev" {
value = "${aws_subnet.subnet-aza-dev.id}"
}
output "subnet-azb-dev" {
value = "${aws_subnet.subnet-azb-dev.id}"
}
output "subnet-aza-test" {
value = "${aws_subnet.subnet-aza-test.id}"
}
output "subnet-azb-test" {
value = "${aws_subnet.subnet-azb-test.id}"
}
...
I'm then intending to lookup these IDs in another template which is reused to provision multiple environments. Example below shows my second template is calling a module to provision an EC2 instance and is passing through the subnet_id.
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-dev}"
}
What I'd like to do is pass the environment variable as part of the lookup for the subnet_id e.g.
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-${var.environment}"
However I'm aware that variable interpolation isn't supported. I've tried using a map inside of the first terraform template to export them all to a 'subnet' which I could then use to lookup from the second template. This didn't work as I was unable to output variables inside of the map.
This sort of design pattern is something I've used previously with CloudFormation, however I'm much newer to terraform. Am I missing something obvious here?
Worked out a way to do this using data sources
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.aws_subnet.subnet-aza.id}"
}
data "aws_subnet" "subnet-aza" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-aza"]
}
}
data "aws_subnet" "subnet-azb" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-azb"]
}
}
Whilst this works and fulfils my original need, I'd like to improve on this by moving the data blocks to within the module, so that there's less repetition. Still working on that one though...

Resources