Tuesday, March 15, 2022

[SOLVED] How to pass/concat variable into the `data.aws_ami` section in the `aws_instance` resource

Issue

I am having difficulties with defining a variable inside the ami=data.aws_ami.$var.ami_name.id line.

I have tried ami= "${data.aws_ami.(var.ami_name).id}"but in both cases I am getting the:

79:   ami = data.aws_ami.(var.ami_name)).id
│ 
│ An attribute name is required after a dot.

It only works with the string value data.aws_ami.ubuntu-1804.id.

My question is how to concat the variable to the data.aws_ami?

The end goal is to provision based on different OS ec2 instances (Suse,Ubuntu,RHEL) All depending on the variable provided when deploying it.

variable "ami_name" {
  default = "ubuntu"
}

data "aws_ami" "amazon" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "linux" {
  key_name = var.ami_key_pair_name
  //ami           = var.ami_id
  //I want this to be dynamic so I can deploy either Amazon or Ubuntu in all regions. 
  ami            = data.aws_ami.$var.ami_name.id 
  //ami           = data.aws_ami.ubuntu.id # this works
  instance_type = "t2.micro"
tags = {
    Name = var.instance_name
  }
  vpc_security_group_ids = [
    aws_security_group.allow-ssh-http.id
  ]
}

I did the search but could not find anything related. I am using Terraform v0.15.4


Solution

The code you show: data.aws_ami.$var.ami_name.id that is not valid terraform syntax.

Here is a possibility for what you are asking:

provider "aws" { region = "us-east-2" }

locals {
    allowed_os = {
        "amazon": {owner: "amazon",       filter: "amzn2-ami-hvm*"},
        "suse":   {owner: "amazon",       filter: "*suse*"},
        "RHEL":   {owner: "amazon",       filter: "*RHEL*"},
        "ubuntu": {owner: "099720109477", filter: "*ubuntu-bionic-18.04-amd64-*"},
    }
}

variable "ami_name" {
  default = "ubuntu"

  validation {
    condition     = can(regex("amazon|suse|RHEL|ubuntu", var.ami_name))
    error_message = "Invalid ami name, allowed_values = [amazon suse RHEL ubuntu]."
  }
}

data "aws_ami" "os" {
  for_each = local.allowed_os

  most_recent = true
  owners      = [each.value.owner]
  filter {
    name   = "name"
    values = [each.value.filter]
  }
}

resource "aws_instance" "linux" {
  ami           = data.aws_ami.os[var.ami_name].id
  instance_type = "t2.micro"
  # ... todo add arguments here
}

My approach here is to use a for_each in the aws_ami, that will give us an array, we can consume that later in the aws_instance resource:

  • data.aws_ami.os["ubuntu"].id
    Here we use a hardcoded value to access a specific AMI in your code.

  • data.aws_ami.os[var.ami_name].id
    Or this way with the variable that will be provided by user or a config file.

You can add more items to the array to add other operating systems, and same with the filters, you can just change the allowed_os local variable to suit your needs.

As an extra, I added validation to your ami_name variable to match the allowed different OS we use in the for_each, that way we prevent any issues right before they can cause errors.



Answered By - Helder Sepulveda
Answer Checked By - Timothy Miller (WPSolving Admin)