I'm following a terraform tutorial on using variables in AWS.
It defines AMI variables like:
variable "amis" {
type = "map"
default = {
"us-east-1" = "ami-b374d5a5"
"us-west-2" = "ami-fc0b939c"
}
}
And then assigns the AMI variable like this:
resource "aws_instance" "example" {
ami = var.amis[var.region]
instance_type = "t2.micro"
}
If I try the example that way I get an error:
Error: Invalid index
on main.tf line 17, in resource "aws_instance" "example":
17: ami = var.amis[var.region]
The given key does not identify an element in this collection value: string
required.
However I can set the variable by hard coding it:
var.amis["us-west-2"] # <-- this works
What can I do to set the variable correctly with ami = var.amis[var.region] ?
you need to define variable var.region or you can pass the value to variable during plan / apply as terraform plan -var 'region=us-west-2' and terraform apply -var 'region=us-west-2'
Related
I'm new to terraform and could use some help please. I had some basic config to build a VPC and two subnets with instances. This ran successfully when I did a 'terraform apply'. Now running a terraform destroy and getting the error in the title. Even running terraform plan to see if anything has changed just throws the same error. The full error says
each.value is object with no attributes
This object does not have an attribute named "az".
I'm guessing there's something relating to the 'for_each' function i've not done right. But then i'm not sure how it applied successfully. I've checked and the resources created from the apply are still there.
main.tf
resource "aws_subnet" "iperf_subnet" {
vpc_id = aws_vpc.ireland_vpc.id
for_each = var.private_subnets
cidr_block = each.value.subnet
availability_zone = each.value.az
}
variables.tf
variable "private_subnets" {
type = map(object({}))
}
exercise.tfvars
private_subnets = {
host_a = {
az = "eu-west-1a"
subnet = "172.30.1.0/25"
}
host_b = {
az = "eu-west-1b"
subnet = "172.30.1.128/25"
}
}
You are specifying the type of the variable private_subnets to be map(object({})). Since the object does not have any attributes explicitly specified, Terraform will throw an error.
You should change this to map(map(string)) or to map(object({az = string, subnet = string})) if you want be more specific.
Getting started on Terraform. I am trying to provision an EC2 instance using the following .tf file. I have a default VPC already in my account in the AZ I am trying to provision the EC2 instance.
# Terraform Settings Block
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
#version = "~> 3.21" # Optional but recommended in production
}
}
}
# Provider Block
provider "aws" {
profile = "default"
region = "us-east-1"
}
# Resource Block
resource "aws_instance" "ec2demo" {
ami = "ami-c998b6b2"
instance_type = "t2.micro"
}
I do the following Terraform commands.
terraform init
terraform plan
terraform apply
aws_instance.ec2demo: Creating...
Error: Error launching source instance: VPCIdNotSpecified: No default VPC for this user. GroupName is only supported for EC2-Classic and default VPC.
status code: 400, request id: 04274b8c-9fc2-47c0-8d51-5b627e6cf7cc
on ec2-instance.tf line 18, in resource "aws_instance" "ec2demo":
18: resource "aws_instance" "ec2demo" {
As the error suggests, it doesn't find the default VPC in the us-east-1 region.
You can provide the subnet_id within your VPC to create your instance as below.
resource "aws_instance" "ec2demo" {
ami = "ami-c998b6b2"
instance_type = "t2.micro"
subnet_id = "subnet-0b1250d733767bafe"
}
I'm only create a Default VPC in AWS
AWS VPC
actions
create default VPC
it's done, you try again now
terraform plan
terraform apply
As of now if there is any default VPC available in the AWS account then using terraform resource aws_instance instance can be created without any network spec input.
Official AWS-terraform example:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#basic-example-using-ami-lookup
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id ## Or use static AMI ID for testing.
instance_type = "t3.micro"
}
The error message: Error: Error launching source instance: VPCIdNotSpecified: No default VPC for this user. states that the EC2 instance did not find any networking configuration in your terraform code where it needs to create the instance.
This is probably because of the missing default VPC in your AWS account and it seems that you are not passing any network config input to terraform resource.
Basically, you have two ways to fix this
Create a default VPC and then use the same code.
Document: https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html#create-default-vpc
Another and better way would be to inject the network config to aws_instance resource. I have used the example from the official aws_instance resource. Feel free to update any attributes accordingly.
resource "aws_vpc" "my_vpc" {
cidr_block = "172.16.0.0/16"
tags = {
Name = "tf-example"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "172.16.10.0/24"
availability_zone = "us-west-2a"
tags = {
Name = "tf-example"
}
}
resource "aws_network_interface" "foo" {
subnet_id = aws_subnet.my_subnet.id
private_ips = ["172.16.10.100"]
tags = {
Name = "primary_network_interface"
}
}
resource "aws_instance" "foo" {
ami = "ami-005e54dee72cc1d00" # us-west-2
instance_type = "t2.micro"
network_interface {
network_interface_id = aws_network_interface.foo.id
device_index = 0
}
credit_specification {
cpu_credits = "unlimited"
}
}
Another way of passing network config to ec2 instance is to use subnet_id in aws_instance resource as suggested by others.
Are there probabilities that you deleted the default vpc, if u did u can recreate going to the VPC Section -> My Vpcs -> At the right corner you will see a dropdown called actions click and select create a default vpc
As the AWS announcement last year On August 15, 2022 we expect all migrations to be complete, with no remaining EC2-Classic resources present in any AWS account. From now on you will need to specify while you are creating any new resources the subnet_id and declare it inside your while creating.
Example :
resource "aws_instance" "test" {
ami = "ami-xxxxx"
instance_type = var.instance_type
vpc_security_group_ids = ["sg-xxxxxxx"]
subnet_id = "subnet-xxxxxx"
I need to pass the below templatefile function to user_data in EC2 resource. Thank you
userdata.tf
templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
ec2.tf
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
# how do I pass the templatefile Funtion here
user_data = ...
tags = {
Name = "HelloWorld"
}
}
Because templatefile is a built-in function, you can call by including it directly in the argument you wish to assign the value to:
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
user_data = templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
tags = {
Name = "HelloWorld"
}
}
The above is a good approach if the template is defined only for one purpose, as seems to be the case here, and you won't be using that result anywhere else. In situations where you want to use the same template result in multiple locations, you can use a local value to give that result a name which you can then use elsewhere in the module:
locals {
web_user_data = templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
}
resource "aws_instance" "web" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"
user_data = local.web_user_data
tags = {
Name = "HelloWorld"
}
}
With the local value web_user_data defined, you can use local.web_user_data to refer to it elsewhere in the same module, and thus use the template result in multiple locations. However, I would suggest doing that only if you need to use the result in multiple locations; if the template result is only for this specific instance's user_data then putting it inline as in my first example above will keep things simpler and thus hopefully easier to understand for a future reader and maintainer.
I am getting below error when I am trying to do use data block for AMI's:-
Error: module.ec2-wf.var.instance_type: variable instance_type in module ec2-wf should be type string, got map
Error: module.ec2-wf.var.ami: variable ami in module ec2-wf should be type string, got map
make: *** [validate] Error 1
Below is my terraform structure:-
project
modules
app1
app2
app3
common
global-variables.tf
main.tf
makefile
provider.tf
vpc.tf
global
acm
alb
asg
ec2
efs
lc
rds
redis
subapp
ec2
main.tf
makefile
provider.tf
variable.tf
project/modules/global/subapp/ec2/main.tf
module "ec2-wf" {
source = "../../../global/ec2"
name = "${var.name}"
db_remote_state_bucket = "s3-terraform-state"
db_remote_state_key = "subapp/ec2/terraform.tfstate"
key_name = "${lookup(var.key_name, terraform.workspace)}"
# ami = "${lookup(var.ami, terraform.workspace)}"
# instance_type = "${lookup(var.instance_type, terraform.workspace)}"
ami = "${var.ami}"
instance_type = "${var.instance_type}"
tags = {
Name = "${var.project}"
Environment = "${lookup(var.env, terraform.workspace)}"
}
}
project/modules/global/ec2/variables.tf
variable "instance_type" {
description = "This describes the Map the environment whether it is dev/test/prd etc"
}
variable "ami" {
description = "This describes the Map of Availability Zones to deploy"
default = ""
}
It looks fine to me. Potentially it looks like it is looking at the commented lines for some reason? As the uncommented versions look fine.
# ami = "${lookup(var.ami, terraform.workspace)}"
# instance_type = "${lookup(var.instance_type, terraform.workspace)}"
To make sure, you could always specify the type (like below). If that doesn't work, delete the commented lines and see if it still happens.
variable "ami" {
type = string
}
I have created the following terraform.tfvars file:
ec2_image = "ami-00035f41c82244dab"
ec2_instance_type = "t2.micro"
And use it as follows in a main.tf file:
resource "aws_instance" "OneServer" {
ami = "${var.ec2_image}"
instance_type = "${var.ec2_instance_type}"
}
Then I execute the 'terraform plan' command and it complains with:
Error: resource 'aws_instance.OneServer' config: unknown variable
referenced: 'ec2_image'; define it with a 'variable' block
So I changed the main.tf file as follows:
variable "ec2_image" {}
variable "ec2_instance_type" {}
resource "aws_instance" "OneServer" {
ami = "${var.ec2_image}"
instance_type = "${var.ec2_instance_type}"
}
Then the command 'terraform plan' works OK.
I don't understand why these variable blocks are required. What's the point for it?
Are you actually using the -var-file command-line switch?
For example, I don't use that switch - I just define my overridable variables in a random tf file (named in my case variable.tf). If the -var-file switch is not used, that means the variable block is required to tell Terraform when a variable is being defined.