Yamldecode map several values same variable

Hello guys,
I have an ansible vars yaml file
and conditional variable such as:

---
ec2_vpc_subnet_id: "{{
  'subnet-d821f591'          if availability_zone == 'eu-west-1a' else
  'subnet-f4534193'          if availability_zone == 'eu-west-1b' else
  'subnet-d092cd8b'          if availability_zone == 'eu-west-1c' else
  'subnet-0cba06212b6a2b1b2' if availability_zone == 'us-east-1a' else
  'subnet-0568db2b1563bf06d' if availability_zone == 'us-east-1b' else
  'subnet-018916361302f0607' if availability_zone == 'us-east-1c' else
  'subnet-a36e88ca'          if availability_zone == 'af-south-1a' else
  'subnet-9b2828e3'          if availability_zone == 'af-south-1b' else
  'subnet-4b6a4801'          if availability_zone == 'af-south-1c' }}"

TF prints the same output as noted in yaml

output "log" {
  value = "${yamldecode(file("./defaults/main.yaml"))["ec2_vpc_subnet_id"]}"
}

Is there any way to map those values or decode in other ways from yaml ?
for example:

'subnet-d821f591' == 'eu-west-1a'
'subnet-f4534193' == 'eu-west-1b'
etc...

Now I separately mapping those values in TF. It could be better to read from one file.

Terraform does not support evaluating Jinja2 template expressions, which is what you have in your Ansible file.

However, you really have a self-inflicted problem here, because you’re representing what could be data (a mapping from availability zone to subnet name) as code.

You should be able to refactor the Ansible YAML to store the data as actual data, and a separate lookup on that data:

ec2_vpc_subnet_ids:
  eu-west-1a: subnet-d821f591
  ... more here ...

ec2_vpc_subnet_id: "{{ ec2_vpc_subnet_ids[availability_zone] }}"

and then you can read the data into Terraform so you can work with it there too.


By the way, this is legacy syntax obsoleted in Terraform 0.13:

Nowadays, just write this, without the "${ }"

  value = yamldecode(file("./defaults/main.yaml"))["ec2_vpc_subnet_id"]

@maxb Nice, thanks !
also have another complexity conditional var

ec2_image_id: "{{
  'ami-0ad028173a4e263b3' if ec2_image == 'centos-stream-9' and region == 'eu-west-1' else
  'ami-03298425565afe6ff' if ec2_image == 'centos-stream-9' and region == 'us-east-1' else
  'ami-007be2411e223714f' if ec2_image == 'centos-stream-9' and region == 'af-south-1' else
  'ami-0b29c1244b006c7d1' if region == 'eu-west-1' else
  'ami-03969aa2deda7e036' if region == 'us-east-1' else
  'ami-0bf44ca2d0ea9ee87' if region == 'af-south-1' }}"

awkward to ask but maybe there is also solution for this ?

think about such solution

ec2_image_ids:
  - region: eu-west-1
    version: 'centos-stream-9'
    image: 'ami-0ad028173a4e263b3'

but don’t know exactly how TF decodes this structure…

I must first admit that I am not very familiar with Ansible, and so this may not be a sensible suggestion, but might it be possible to invert this so that the source of this data is in Terraform and your Terraform configuration generates the Ansible YAML, rather than trying to evaluate the Ansible configuration from inside Terraform?

For example, both of the big Jinja conditional expressions you showed here could in principle be rewritten as a mapping inside Terraform:

variable "region" {
  type = string
}

variable "availability_zone" {
  type = string
}

variable "ec2_image" {
  type = string
}

locals {
  ec2_vpc_subnet_ids = {
    "eu-west-1a" = "subnet-d821f591"
    "eu-west-1b" = "subnet-f4534193"
    "eu-west-1c" = "subnet-d092cd8b"
    # (etc)
  }
  ec2_image_ids = {
    "eu-west-1:centos-stream-9" = "ami-0ad028173a4e263b3"
    "us-east-1:centos-stream-9" = "ami-03298425565afe6ff"
    # (etc)
  }

  ec2_vpc_subnet_id = local.ec2_vpc_subnet_ids[var.availability_zone]
  ec2_image_id      = local.ec2_image_ids["${var.region}:${var.ec2_image}"]

  ansible_yaml = yamlencode({
    ec2_image_id      = local.ec2_image.id
    ec2_vpc_subnet_id = local.ec2_vpc_subnet_id
  })
}

This uses Terraform itself to perform the lookups, instead of using Jinja templates. That means that the mapping tables live inside the Terraform configuration instead of in the YAML template, and local.ansible_yaml will be a string containing literal YAML values that don’t require any further expansion to use them:

"ec2_image_id": "ami-0ad028173a4e263b3"
"ec2_vpc_subnet_id": "subnet-d821f591"

Elsewhere in your Terraform module you can refer to local.ec2_image_id and local.ec2_vpc_subnet_id to get those isolated values, without the need to decode YAML first to get them (because the data is already in Terraform’s scope.)

thanks @apparentlymart for Your suggestion
I found similar solution as you mentioned, but with yamldecode
restructured ansible vars in such form

---
ec2_image_ids:
  eu-west-1:
    centos-stream-9: ami-0ad028173a4e263b3
    centos-stream-8: ami-0b29c1244b006c7d1
  us-east-1:
    centos-stream-9: ami-03298425565afe6ff
    centos-stream-8: ami-03969aa2deda7e036
  af-south-1:
    centos-stream-9: ami-007be2411e223714f
    centos-stream-8: ami-0bf44ca2d0ea9ee87

ec2_vpc_subnet_ids:
  eu-west-1a: subnet-d821f591
  eu-west-1b: subnet-f4534193
  eu-west-1c: subnet-d092cd8b
  us-east-1a: subnet-0cba06212b6a2b1b2
  us-east-1b: subnet-0568db2b1563bf06d
  us-east-1c: subnet-018916361302f0607
  af-south-1a: subnet-a36e88ca
  af-south-1b: subnet-9b2828e3
  af-south-1c: subnet-4b6a4801

main-variables.tf

variable "zone" { default = "a" }
variable "image" { default = "centos-stream-8" }
variable "region" { default = "af-south-1" }
locals {
    image = yamldecode(file("./defaults/main.yaml"))["ec2_image_ids"]["${var.region}"]["${var.image}"]
}

locals {
    subnet = yamldecode(file("./defaults/main.yaml"))["ec2_vpc_subnet_ids"]["${var.region}${var.zone}"]
}

locals {
    zone = "${var.region}${var.zone}"
}