Remove extra brackets and quotes from output.tf string to use in EC2 subnet_id input

Hi,

I am trying to launch two EC2 instances across two subnets which are in individual AZ’s. For various reasons, among them Oracle licenses and Ansible provisioning by EC2 name, I can not use an autoscale group (at least I’m pretty sure I cant).

I have got this far

resource "aws_instance" "instance" {
  count                  = length(split(",", var.vpc_public_subnets))
  ami                    = data.aws_ami.rhel7.id
  instance_type          = "t2.micro"
  subnet_id              = element(split(",", local.public_subnets), count.index)
  vpc_security_group_ids = ["${module.security_group.security_group_id}"]
  tags                   = merge(var.tags, { Name = "${var.shortcode}-${var.env_namespace}-web${count.index + 1}" }, )
}

This plans correctly but I know it will fail on creation as the subnet_id’s contain extra characters

+ subnet_id                            = "[\"subnet-06c77c67a83fe1156\""
+ subnet_id                            = "\"subnet-004a5ade7bfe062ac\"]"

my vpc public outputs is in the format

private_subnets = [
  "subnet-0144c36cfdacde2b5",
  "subnet-032c92874a0bd532e",
]

I have tried changing the subnet_id line to

 subnet_id              = trim("${element(split(",", var.vpc_public_subnets, ), count.index)}", "\"")

and get

+ subnet_id                            = "[\"subnet-06c77c67a83fe1156"
+ subnet_id                            = "subnet-004a5ade7bfe062ac\"]"

I feel I am close but just can not get it right, could anyone help please?

You shouldn’t wrap variables in the “${…}” wrapper that you are currently doing.

Instead you need to just reference the variable directly. For example vpc_security_group_ids = module.security_group.security_group_id

yes it does (at least in my code)

│ Error: Incorrect attribute value type
│
│   on main.tf line 165, in resource "aws_instance" "instance":
│  165:   vpc_security_group_ids = module.security_group.security_group_id
│     ├────────────────
│     │ module.security_group.security_group_id is a string, known only after apply
│
│ Inappropriate value for attribute "vpc_security_group_ids": set of string
│ required.

I DID IT !!

Here are the steps I took…

in my vpc output declare the individual subnets

output "public_subnets_0" {
  description = "List of IDs of public subnets"
  value       = module.vpc.public_subnets[0]
}

output "public_subnets_1" {
  description = "List of IDs of public subnets"
  value       = module.vpc.public_subnets[1]
}

in my ec2 variables.tf create a map of objects

variable "ec2_to_subnet" {
  description = "map of objects to put ec2 in each subnet with individual names"
  type = map(object({
      subnet = string
  }))
}

define the values for the vars in whatever way you do, I use Terragrunt

ec2_to_subnet = { 
      web-01 = {
        subnet = dependency.vpc.outputs.public_subnet_0
      }
      web-02 = {
        subnet = dependency.vpc.outputs.public_subnet_1
      }
    }

update the resource to use a for_each

resource "aws_instance" "instance" {
  for_each                 = var.ec2_to_subnet
  ami                      = data.aws_ami.rhel7.id
  instance_type            = "t2.micro"
  subnet_id                = each.value["subnet"]
  vpc_security_group_ids   = ["${module.security_group.security_group_id}"]
  tags                     = merge(var.tags, { Name = "${var.shortcode}-${var.env_namespace}-${each.key}" }, )
}

This means I can add any other specifics I need into the vars and call them with a different each.value.[“xyz”]

I also found how to get the instances out as usable Ansible inventory (lifted from elsewhere)

resource "local_file" "inventory" {
  content = templatefile("inventory.tmpl", { content = tomap({
    for instance in aws_instance.instance :
    instance.tags.Name => instance.public_dns
    })
  })
  filename = format("%s/%s", abspath(path.root), "inventory.yaml")
}

inventory.tmpl

all:
  children:
    ingress:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("ingress", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    master:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("master", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    nodes:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("web", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}

The line vpc_security_group_ids = ["${module.security_group.security_group_id}"] still isn’t quite right. It should be vpc_security_group_ids = [module.security_group.security_group_id]

1 Like