arif
arif

Reputation: 679

terraform - how to use variables inside attributes

I am not sure if this is the right approach to do this but I want to use a variable as an attribute.

For example, I have a variable that changes based on user input: os_name = ubuntu.

I want to use this variable name like the following,

resource "aws_instance" "workerNode" {
  ..................
  ami = data.aws_ami.${var.os_name}.image_id
  ..................
}

Following is an example of the data block,

data "aws_ami" "suse" {
    count  = "${var.os_name == "suse" ? 1 : 0}"
    owners = ["amazon"]
    most_recent = true
    filter {
        name = "name"
        values = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
    }
}    

Which result the following,

  "architecture" = "x86_64"
  "hypervisor" = "xen"
  "id" = "ami-0d3905203a039e3b0"
  "image_id" = "ami-0d3905203a039e3b0"

But terraform is not allowing me to do this. Is there any way I can do this or I have to change the workflow?

Upvotes: 1

Views: 2958

Answers (3)

Martin Atkins
Martin Atkins

Reputation: 74694

In situations where it's not appropriate to gather all of your instances under a single resource using for_each (which would implicitly make that resource appear as a map of objects), you can get a similar result explicitly by writing a local value expression to construct an equivalent map:

locals {
  amis = {
    suse   = data.aws_ami.suse
    ubuntu = data.aws_ami.ubuntu
  }
}

Then you can refer to local.amis["ubuntu"] or local.amis["suse"] (possibly replacing the element key with a variable, if you need to.


With that said, it does seem like there is a possible different approach for your case which would get there with only one data block:

locals {
  os_ami_queries = {
    suse = {
      owners = ["amazon"]
      filters = {
        name = ["suse-sles-${var.os_version}-sp*-v????????-hvm-ssd-x86_64"]
      }
    }
    ubuntu = {
      owners = ["amazon"]
      filters = {
        name = ["ubuntu-${var.os_version}-something-something"]
      }
    }
  }
  ami_query = local.os_ami_queries[var.os_name]
}

data "aws_ami" "selected" {
  owners = local.ami_query.owners

  dynamic "filter" {
    for_each = local.ami_query.filters
    content {
      name   = filter.key
      values = filter.value
    }
  }
}

This different permutation does the OS selection before the data "aws_ami" lookup, so it can use the settings associated with whichever OS was selected by the caller. The AMI id would then be in data.aws_ami.selected.id.

With that said, this approach has the disadvantage of being quite indirect and using a dynamic block, so I'd weigh that against the readability of the alternatives to pick the one which seems subjectively easiest to follow for someone who isn't familiar with this configuration. There isn't a single answer to that because to some extent it's a matter of taste, and so if you are working in a team setting this could be something to discuss with colleagues to see which approach best suits tradeoffs like how often you expect to be adding and removing supported operating systems vs. changing the details of how you use the result.

Upvotes: 3

arif
arif

Reputation: 679

I have solved the issue just by using conditional expression.

I am not sure if it is a standard way of doing things but it works for me.

I have tried to emulate if/elif/else with nested conditional expression.

output "name" {
    value = "${ var.os_name == "ubuntu" ? data.aws_ami.ubuntu[0].image_id : (var.os_name == "redhat" ? data.aws_ami.redhat[0].image_id : (var.os_name == "centos" ? data.aws_ami.suse[0].image_id : data.aws_ami.suse[0].image_id ))}"
}

Upvotes: 0

yvesonline
yvesonline

Reputation: 4857

You can make it work by specifying your AMI's with a for_each and thus getting a map which you can access by key.

My data.aws_ami.myamis looks like this:

data "aws_ami" "myamis" {
  for_each = toset(["suse", "ubuntu"])

  most_recent = true
  owners      = ["amazon"]

  filter {
    name = "name"
    values = ["${each.value}*"]
  }
}

For test purposes I define a variable foo like this:

variable "foo" {
  type    = string
  default = "suse"
}

Now I can access the AMI like this:

$ tf console
> data.aws_ami.myamis[var.foo].image_id
"ami-0ea50c090ba6e85c5"

You can adapt this to suit your needs for os_name and os_version.

Upvotes: 1

Related Questions