Trying to lunch 1 or 2 instances based on a condition ( "single-target" )
ERROR im getting :
132: target_id = var.ec2-2
var.ec2-2 is empty tuple
Inappropriate value for attribute "target_id": string required.
the use of that variable :
resource "aws_lb_target_group_attachment" "b" {
target_id = var.ec2-2
}
outputs :
output "ec2-2_ot" {
value = aws_instance.ec2V2[*].id
description = "the value of the network module ec2-2 id"
}
variable :
variable "single-target" {
type=bool
default=false
}
main.tf :
module "network_module" {
ec2-2 = module.compute_module.ec2-2_ot
}
ec2 launch :
resource "aws_instance" "ec2V2" {
count = var.single-target ? 0 : 1
ami = var.ec2_ami
instance_type = var.ec2_type
associate_public_ip_address = true
key_name = var.key_pair
vpc_security_group_ids = [ var.vpc-sg ]
subnet_id = var.subnet-2
user_data = file("PATH/user-data.sh")
tags = {
Name = var.ec2-2_name
}
}
As the count has been already set to the resource you are trying to create, the returning result list would need to be accessed via Splat Expression mentioned in this terraform documentation.
If you need to choose specific index from the list of the result , then you can use the element function to do so
So in your case, if you need to output EC2 id it would be as follows.
output "thisisoutput" {
value = aws_instance.ec2V2[*].arn
}
Related
I am setting tags to AMI. I have ubuntu and EKS AMI, where EKS AMI needs to be set with k8s version, containerd etc which are not required for other OS. I have declared all variables that are EKS specific to default to null in an assumption that they will skipped while I run for_each but it errors out. Here is the code and error.
locals {
tags = {
docker_version = var.docker_version
kubernetes = var.k8s_version
cni_plugin_version = var.cni_plugin_version
containerd_version = var.containerd_version
target = var.target
source_ami_id = var.source_ami_id
Release = var.ami_release-version
Description = var.ami_description
Name = var.ami_name
Creator = var.creator
}
data "aws_ami" "ami_image" {
most_recent = true
owners = ["xxxxxxxx"]
name_regex = var.ami_regex
}
output "ami_id" {
value = data.aws_ami.ami_image.id
}
output "ami_arn" {
value = data.aws_ami.ami_image.arn
}
output "ami_name" {
value = data.aws_ami.ami_image.name
}
resource "aws_ec2_tag" "ami-taggging" {
resource_id = data.aws_ami.ami_image.id
for_each = local.tags
key = each.key
value = each.value
}
Error when values are null:
Error: Missing required argument
on main.tf line 41, in resource "aws_ec2_tag" "ami-tagging":
41: value = each.value
Is there a way to skip or gracefully move to next record if the value is null.
Assuming that default_tags (not shown in your question) is similar to your local.tag, you could do the following:
resource "aws_ec2_tag" "ami-taggging" {
resource_id = data.aws_ami.ami_image.id
for_each = {for k,v in local.tags: k => v if v != null}
key = each.key
value = each.value
}
The solution uses for expression. Basically, it takes your original local.tags and creates new temporary map that will be used in the for_each. The new map will have same keys and values (k=>v) except those that don't meet the if condition.
I have multiple EC2 instances created using for each. Each instance is being deployed into a different subnet. I am getting an error when trying to apply tags to each instance being deployed. Any advice would be helpful. Below is the code for my tags and instances:
resource "aws_instance" "private" {
for_each = aws_subnet.private
ami = var.ec2_amis[var.region]
instance_type = var.tableau_instance
key_name = aws_key_pair.tableau.key_name
subnet_id = each.value.id
tags = {
Name = var.ec2_tags[each.key]
}
}
variable "ec2_tags" {
type = list(string)
default = [
"PrimaryEC2",
"EC2Worker1",
"EC2Worker2"
]
}
Error
Error: Invalid index
on vpc.tf line 21, in resource "aws_instance" "private":
21: Name = var.ec2_tags[each.key]
|----------------
| each.key is "3"
| var.ec2_tags is list of string with 3 elements
The given key does not identify an element in this collection value.
I had this code working earlier, not sure what happened. I made a change to the AMI it spins up, but I don't see why that could have an effect on tags. Any advice would be helpful.
UPDATE
I have updated the resource with the following locals block and dynamic block within my "aws_instance" "private" code:
locals {
private_instance = [{
name = "PrimaryEC2"
},
{
name = "EC2Worker1"
},
{
name = "EC2Worker2"
}]
}
dynamic "tags" {
for_each = local.private_instance
content {
Name = tags.value.name
}
}
Error
Error: Unsupported block type
on vpc.tf line 28, in resource "aws_instance" "private":
28: dynamic "tags" {
Blocks of type "tags" are not expected here.
Any advice how to fix would help. Thanks!
If you want to make your tags dynamic, you could create them as follows:
tags = {
Name = each.key == "0" ? "PrimaryEC2" : "EC2Worker${each.key}"
}
You would use it as follows (assuming everything else is OK):
resource "aws_instance" "private" {
for_each = aws_subnet.private
ami = var.ec2_amis[var.region]
instance_type = var.tableau_instance
key_name = aws_key_pair.tableau.key_name
subnet_id = each.value.id
tags = {
Name = each.key == "0" ? "PrimaryEC2" : "EC2Worker${each.key}"
}
}
The code uses conditional expression. It works as follows.
If each.key is equal to "0" (i.e., first instance being created) then its tag will be "PrimaryEC2". All remaining instances will be tagged: "EC2Worker1", "EC2Worker2", "EC2Worker3" and so on for as many subnets there are.
One possible cause of this errors is that the aws_subnet.private variable is longer then the list of ec2 tags which would result in an error when the index 3 is used on your ec2_tags list looking for the 4th (nonexistent element).
I am using below code to create 3 ec2 instances and register them with a load balancer,
variables
instance_type = "t3.large"
root_block_volume_type = "standard"
root_block_volume_size = "50"
instance_count = "3"
ec2 creation
resource "aws_instance" "ec2" {
count = "${var.instance_count}"
ami = "${var.ami_id}"
instance_type = "${var.instance_type}"
key_name = "${var.key_pair_name}"
subnet_id = "${var.private_subnet_id}"
iam_instance_profile = "${aws_iam_instance_profile.iam_instance_profile.name}"
/*
* CAUTION: changing value of below fields will cause the EC2 instance to be terminated and
* re-created. Think before running the "apply" command.
*/
associate_public_ip_address = false
tags = {
Environment = "${var.env}"
Project = "${var.project}"
Provisioner="different-box"
Name = "${local.name}-1"
}
root_block_device {
volume_type = "${var.root_block_volume_type}"
volume_size = "${var.root_block_volume_size}"
}
}
resource "aws_network_interface_sg_attachment" "sg_attachment" {
count = "${var.instance_count}"
security_group_id = "${aws_security_group.ec2_sg.id}"
network_interface_id = "${aws_instance.ec2[count.index].primary_network_interface_id}"
}
Registering with load balancer
resource "aws_alb_target_group_attachment" "alb_target_group_attachment" {
count = length("${var.ec2_instance_ids}")
target_group_arn = "${aws_alb_target_group.alb_target_group.arn}"
target_id = "${var.ec2_instance_ids[count.index]}"
port = "${var.alb_target_group_port}"
}
But however when I pass ec2 instance ids as below,
module "alb_engine-ui" {
source = "./modules/load-balancer"
env = "${lower(var.env)}"
project = "engine-ui"
vpc_id = "${data.aws_vpc.main.id}"
public_subnet1_id = "${var.public_subnet1_id}"
public_subnet2_id = "${var.public_subnet2_id}"
health_check_target_group_path = "/"
certificate_arn = "${var.certificate_arn}"
alb_target_group_port = "2016"
ec2_instance_ids = ["${aws_instance.ec2[*].id}"]
}
variable "ec2_instance_ids" {
description = "the ec2 instance ids to be used for alb target group"
type = "list"
}
I'm getting the below error,
Error: Incorrect attribute value type
on provisioners/different-box/modules/load-balancer/resources.tf line 109, in resource "aws_alb_target_group_attachment" "alb_target_group_attachment":
109: target_id = "${var.ec2_instance_ids[count.index]}"
Inappropriate value for attribute "target_id": string required.
Is there a way to avoid this error and pass ec2 ids to the list?
"${aws_instance.ec2[*].id}" is already a list of IDs so if you wrap it in square brackets it makes it a list of lists.
Removing the square brackets instead leaves it as a list of strings so when you index it you get a single string which is what the target_id parameter wants.
I have a two Launch Config creation resources in Terraform: one for spot pricing and one for reserve pricing - with the choice on which to use based on a "use_spot_pricing" boolean variable. I need to return the Launch Config ID from whichever resource was used. The problem is that the conditional blows up saying the Launch Config ID was not found for the resource that wasn't created.
My code looks like:
resource "aws_launch_configuration" "launch_config_reserved_pricing" {
// If use_spot_pricing is true (which translates to 1), this resource is not created (i.e. count = 0).
count = "${1 - var.use_spot_pricing}"
name_prefix = "${var.resource_name_prefix}${var.envSuffix}-"
image_id = "${var.generic_ami_id}"
instance_type = "${var.instance_type}"
key_name = "${var.key_name}"
security_groups = ["${var.vpc_security_group_ids}"]
iam_instance_profile = "${var.iam_instance_profile}"
user_data = "${data.template_file.lc_user_data.rendered}"
}
resource "aws_launch_configuration" "launch_config_spot_pricing" {
// If use_spot_pricing is true (which translates to 1), this resource is created once. Otherwise the previous one is.
count = "${var.use_spot_pricing}"
name_prefix = "${var.resource_name_prefix}${var.envSuffix}-"
image_id = "${var.generic_ami_id}"
instance_type = "${var.instance_type}"
key_name = "${var.key_name}"
security_groups = ["${var.vpc_security_group_ids}"]
iam_instance_profile = "${var.iam_instance_profile}"
user_data = "${data.template_file.lc_user_data.rendered}"
spot_price = "${var.spot_price}"
}
output "launch_config_id" {
value = "${ var.use_spot_pricing == true ? aws_launch_configuration.launch_config_spot_pricing.id : aws_launch_configuration.launch_config_reserved_pricing.id }"
}
This results in the errors (the first when I the spot pricing resource is used and the second when the reserve pricing resource is used):
* module.create_launch_configs.module.parser.output.launch_config_id: Resource 'aws_launch_configuration.launch_config_reserved_pricing' not found for variable 'aws_launch_configuration.launch_config_reserved_pricing.id'
* module.create_launch_configs.module.filter.output.launch_config_id: Resource 'aws_launch_configuration.launch_config_spot_pricing' not found for variable 'aws_launch_configuration.launch_config_spot_pricing.id'
This work around failed:
output "launch_config_id" {
value = "${coalesce(aws_launch_configuration.launch_config_spot_pricing.id , aws_launch_configuration.launch_config_reserved_pricing.id ) }"
}
This work around failed too:
output "launch_config_id" {
value = "${coalesce( join( "" , aws_launch_configuration.launch_config_spot_pricing.id ) , join( "" , aws_launch_configuration.launch_config_reserved_pricing.id ) ) }"
}
Also tried using 1 instead of true, no luck:
output "launch_config_id" {
value = "${ var.use_spot_pricing == 1 ? aws_launch_configuration.launch_config_spot_pricing.id : aws_launch_configuration.launch_config_reserved_pricing.id }"
}
In case anyone from Hashicorp is reading, a conditional shouldn't fail if the value not selected is undefined. It doesn't need to be examined, only the value for the condition that passes.
While it can be annoying that Terraform won't shortcut and not evaluate the false side of a conditional in this particular case you don't need it because you can just default the spot price to an empty string and then if it's not provided you get an on demand instance launch config.
So instead of having to do what you're currently doing instead you can just do:
variable "spot_price" {
default = ""
}
resource "aws_launch_configuration" "launch_config" {
name_prefix = "${var.resource_name_prefix}${var.envSuffix}-"
image_id = "${var.generic_ami_id}"
instance_type = "${var.instance_type}"
key_name = "${var.key_name}"
security_groups = ["${var.vpc_security_group_ids}"]
iam_instance_profile = "${var.iam_instance_profile}"
user_data = "${data.template_file.lc_user_data.rendered}"
spot_price = "${var.spot_price}"
}
output "launch_config_id" {
value = "${aws_launch_configuration.launch_config.id}"
}
I have created a AWS instance list using Terraform:
resource "aws_instance" "masters" {
count = 2
ami = "${var.aws_centos_ami}"
instance_type = "t2.micro"
availability_zone = "eu-west-1b"
tags {
Name = "master-${count.index}"
}
}
How can I assign volumes to that instances like in a loop?
I just trying using the next:
data "aws_ebs_volume" "masters_ebs_volume" {
most_recent = true
filter {
name = "attachment.instance-id"
values = ["${aws_instance.masters.*.id}"]
}
}
But I don't thing it is working fine, because when I try to write the AWS volumes in a file, it writes only one volume name.
provisioner "local-exec" {
command = "echo \"${join("\n", data.aws_ebs_volume.masters_ebs_volume.*.id)}\" >> volumes"
}
I have tried defining the volume like this:
data "aws_ebs_volume" "masters_ebs_volume" {
count = 2
# most_recent = true
filter {
name = "attachment.instance-id"
values = ["${aws_instance.masters.*.id}"]
}
}
But it throws the next error:
data.aws_ebs_volume.masters_ebs_volume.0: Your query returned more than one result. Please try a more specific search criteria, or set `most_recent` attribute to true.
You'll need to tell it which instance specifically maps to which volume. You can do that with element():
data "aws_ebs_volume" "masters_ebs_volume" {
count = 2
filter {
name = "attachment.instance-id"
values = ["${element(aws_instance.masters.*.id, count.index)}"]
}
}